diff --git a/.gitignore b/.gitignore index 9115ebc..a37f180 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ client/calls/ /gordio /calls Session.vim +*.log diff --git a/config.sample.yaml b/config.sample.yaml index b98b24a..dc4b2b1 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -14,3 +14,10 @@ auth: "localhost": true listen: ':3050' public: true +log: + # level logs at or above the specified level + # levels are trace, debug, info, warn, error, fatal, panic + # an entry without a file: property indicates stderr + - level: debug + - level: error + file: error.log diff --git a/pkg/gordio/config/config.go b/pkg/gordio/config/config.go index c8f0a8b..a33c4ef 100644 --- a/pkg/gordio/config/config.go +++ b/pkg/gordio/config/config.go @@ -9,11 +9,12 @@ import ( ) type Config struct { - DB DB `yaml:"db"` - CORS CORS `yaml:"cors"` - Auth Auth `yaml:"auth"` - Listen string `yaml:"listen"` - Public bool `yaml:"public"` + DB DB `yaml:"db"` + CORS CORS `yaml:"cors"` + Auth Auth `yaml:"auth"` + Log []Logger `yaml:"log"` + Listen string `yaml:"listen"` + Public bool `yaml:"public"` configPath string } @@ -33,6 +34,11 @@ type DB struct { Driver string `yaml:"driver"` } +type Logger struct { + File *string `yaml:"file"` + Level *string `yaml:"level"` +} + func New(cmd *cobra.Command) *Config { c := &Config{} cmd.PersistentFlags().StringVarP(&c.configPath, "config", "c", "config.yaml", "configuration file") diff --git a/pkg/gordio/server/logging.go b/pkg/gordio/server/logging.go new file mode 100644 index 0000000..03d9930 --- /dev/null +++ b/pkg/gordio/server/logging.go @@ -0,0 +1,110 @@ +package server + +import ( + "io" + "io/fs" + "os" + "os/signal" + "syscall" + + "dynatron.me/x/stillbox/pkg/gordio/config" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +const ( + LOGPERM fs.FileMode = 0600 +) + +type Logger struct { + console io.Writer + writers []io.Writer + hup chan os.Signal +} + +func NewLogger(cfg *config.Config) (*Logger, error) { + l := &Logger{ + console: &zerolog.ConsoleWriter{Out: os.Stderr}, + } + l.hup = make(chan os.Signal, 1) + go func() { + for sig := range l.hup { + log.Logger = log.Output(l.console) + log.Info().Msgf("received %s, closing and reopening logfiles", sig) + l.Close() + err := l.OpenLogs(cfg) + if err != nil { + log.Error().Err(err).Msg("error reopening logs") + continue + } + + log.Logger = log.Output(zerolog.MultiLevelWriter(l.writers...)) + } + }() + + signal.Notify(l.hup, syscall.SIGHUP) + + err := l.OpenLogs(cfg) + if err != nil { + return nil, err + } + + log.Logger = log.Output(zerolog.MultiLevelWriter(l.writers...)) + + return l, nil +} + +func (l *Logger) Close() { + for _, lg := range l.writers { + if _, isConsole := lg.(*zerolog.ConsoleWriter); isConsole { + continue + } + + if cl, isCloser := lg.(io.Closer); isCloser { + err := cl.Close() + if err != nil { + log.Error().Err(err).Msg("closing writer") + } + } + } + + l.writers = nil +} + +func (l *Logger) OpenLogs(cfg *config.Config) error { + l.writers = make([]io.Writer, 0, len(cfg.Log)) + for _, lc := range cfg.Log { + level := zerolog.TraceLevel + if lc.Level != nil { + var err error + + level, err = zerolog.ParseLevel(*lc.Level) + if err != nil { + return err + } + } + + w := &zerolog.FilteredLevelWriter{ + Level: level, + } + + switch lc.File { + case nil: + w.Writer = &zerolog.LevelWriterAdapter{Writer: l.console} + default: + f, err := os.OpenFile(*lc.File, os.O_WRONLY|os.O_CREATE, LOGPERM) + if err != nil { + return err + } + + w.Writer = &zerolog.LevelWriterAdapter{ + Writer: f, + } + } + + l.writers = append(l.writers, w) + } + + return nil +} diff --git a/pkg/gordio/server/server.go b/pkg/gordio/server/server.go index e51fa6c..aace9c0 100644 --- a/pkg/gordio/server/server.go +++ b/pkg/gordio/server/server.go @@ -23,9 +23,15 @@ type Server struct { sources sources.Sources sinks sinks.Sinks nex *nexus.Nexus + logger *Logger } func New(cfg *config.Config) (*Server, error) { + logger, err := NewLogger(cfg) + if err != nil { + return nil, err + } + db, err := database.NewClient(cfg.DB) if err != nil { return nil, err @@ -39,6 +45,7 @@ func New(cfg *config.Config) (*Server, error) { db: db, r: r, nex: nexus.New(), + logger: logger, } srv.sinks.Register("database", sinks.NewDatabaseSink(srv.db), true)