From d108343ced6d4e32cb5bd0a0b88c66b1f5ec55a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Mon, 7 Mar 2022 10:05:02 +0000 Subject: [PATCH] Logs JSON output. --- cmd/root.go | 112 +++++++++++++++++++--------------- internal/config/root.go | 76 ++++++++++++++++++++--- internal/websocket/manager.go | 4 +- 3 files changed, 133 insertions(+), 59 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 79bc4096..187482ee 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -34,49 +34,10 @@ func init() { rootConfig := config.Root{} cobra.OnInitialize(func() { - ////// - // logs - ////// - console := zerolog.ConsoleWriter{Out: os.Stdout} - zerolog.TimeFieldFormat = zerolog.TimeFormatUnix - zerolog.SetGlobalLevel(zerolog.InfoLevel) - - logs := viper.GetBool("logs") - if !logs { - log.Logger = log.Output(console) - } else { - logsPath := filepath.Join(".", "logs") - if runtime.GOOS == "linux" { - logsPath = "/var/log/neko" - } - - if _, err := os.Stat(logsPath); os.IsNotExist(err) { - _ = os.Mkdir(logsPath, os.ModePerm) - } - - latest := filepath.Join(logsPath, "neko-latest.log") - if _, err := os.Stat(latest); err == nil { - err = os.Rename(latest, filepath.Join(logsPath, "neko."+time.Now().Format("2006-01-02T15-04-05Z07-00")+".log")) - if err != nil { - log.Panic().Err(err).Msg("failed to rotate log file") - } - } - - logf, err := os.OpenFile(latest, os.O_RDWR|os.O_CREATE, 0666) - if err != nil { - log.Panic().Err(err).Msg("failed to create log file") - } - - logger := diode.NewWriter(logf, 1000, 10*time.Millisecond, func(missed int) { - fmt.Printf("logger dropped %d messages", missed) - }) - - log.Logger = log.Output(io.MultiWriter(console, logger)) - } - ////// // configs ////// + config := viper.GetString("config") // Use config file from the flag. if config == "" { config = os.Getenv("NEKO_CONFIG") // Use config file from the environment variable. @@ -97,26 +58,81 @@ func init() { viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) viper.AutomaticEnv() // read in environment variables that match + // read config values err := viper.ReadInConfig() if err != nil && config != "" { - log.Err(err) + panic("unable to read config file: " + err.Error()) } // get full config file path config = viper.ConfigFileUsed() + // set root config values + rootConfig.Set() + ////// - // debug + // logs ////// - debug := viper.GetBool("debug") - if debug { - zerolog.SetGlobalLevel(zerolog.DebugLevel) + var logWriter io.Writer + + // log to a directory instead of stderr + if rootConfig.LogDir != "" { + if _, err := os.Stat(rootConfig.LogDir); os.IsNotExist(err) { + _ = os.Mkdir(rootConfig.LogDir, os.ModePerm) + } + + latest := filepath.Join(rootConfig.LogDir, "neko-latest.log") + if _, err := os.Stat(latest); err == nil { + err = os.Rename(latest, filepath.Join(rootConfig.LogDir, "neko."+time.Now().Format("2006-01-02T15-04-05Z07-00")+".log")) + if err != nil { + panic("failed to rotate log file: " + err.Error()) + } + } + + logf, err := os.OpenFile(latest, os.O_RDWR|os.O_CREATE, 0666) + if err != nil { + panic("failed to open log file: " + err.Error()) + } + + logWriter = diode.NewWriter(logf, 1000, 10*time.Millisecond, func(missed int) { + fmt.Printf("logger dropped %d messages", missed) + }) + } else { + logWriter = os.Stderr + } + + // log console output instead of json + if !rootConfig.LogJson { + logWriter = zerolog.ConsoleWriter{ + Out: logWriter, + NoColor: rootConfig.LogNocolor, + } + } + + // save new logger output + log.Logger = log.Output(logWriter) + + // set custom log level + if rootConfig.LogLevel != zerolog.NoLevel { + zerolog.SetGlobalLevel(rootConfig.LogLevel) + } + + // set custom log tiem format + if rootConfig.LogTime != "" { + zerolog.TimeFieldFormat = rootConfig.LogTime + } + + timeFormat := rootConfig.LogTime + if rootConfig.LogTime == zerolog.TimeFormatUnix { + timeFormat = "UNIX" } logger := log.With(). - Bool("debug", debug). - Bool("logs", logs). Str("config", config). + Str("log-level", zerolog.GlobalLevel().String()). + Bool("log-json", rootConfig.LogJson). + Str("log-time", timeFormat). + Str("log-dir", rootConfig.LogDir). Logger() if config == "" { @@ -128,8 +144,6 @@ func init() { logger.Info().Msg("preflight complete with config file") } } - - rootConfig.Set() }) if err := rootConfig.Init(root); err != nil { diff --git a/internal/config/root.go b/internal/config/root.go index b08534af..e9870eda 100644 --- a/internal/config/root.go +++ b/internal/config/root.go @@ -1,29 +1,58 @@ package config import ( + "os" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" ) type Root struct { - Debug bool - Logs bool Config string + + LogLevel zerolog.Level + LogTime string + LogJson bool + LogNocolor bool + LogDir string } func (Root) Init(cmd *cobra.Command) error { + cmd.PersistentFlags().StringP("config", "c", "", "configuration file path") + if err := viper.BindPFlag("config", cmd.PersistentFlags().Lookup("config")); err != nil { + return err + } + + // just a shortcut cmd.PersistentFlags().BoolP("debug", "d", false, "enable debug mode") if err := viper.BindPFlag("debug", cmd.PersistentFlags().Lookup("debug")); err != nil { return err } - cmd.PersistentFlags().BoolP("logs", "l", false, "save logs to file") - if err := viper.BindPFlag("logs", cmd.PersistentFlags().Lookup("logs")); err != nil { + cmd.PersistentFlags().String("log.level", "info", "set log level (trace, debug, info, warn, error, fatal, panic, disabled)") + if err := viper.BindPFlag("log.level", cmd.PersistentFlags().Lookup("log.level")); err != nil { return err } - cmd.PersistentFlags().StringP("config", "c", "", "configuration file path") - if err := viper.BindPFlag("config", cmd.PersistentFlags().Lookup("config")); err != nil { + cmd.PersistentFlags().String("log.time", "unix", "time format used in logs (unix, unixms, unixmicro)") + if err := viper.BindPFlag("log.time", cmd.PersistentFlags().Lookup("log.time")); err != nil { + return err + } + + cmd.PersistentFlags().Bool("log.json", false, "logs in JSON format") + if err := viper.BindPFlag("log.json", cmd.PersistentFlags().Lookup("log.json")); err != nil { + return err + } + + cmd.PersistentFlags().Bool("log.nocolor", false, "no ANSI colors in non-JSON output") + if err := viper.BindPFlag("log.nocolor", cmd.PersistentFlags().Lookup("log.nocolor")); err != nil { + return err + } + + cmd.PersistentFlags().String("log.dir", "", "logging directory to store logs") + if err := viper.BindPFlag("log.dir", cmd.PersistentFlags().Lookup("log.dir")); err != nil { return err } @@ -31,7 +60,38 @@ func (Root) Init(cmd *cobra.Command) error { } func (s *Root) Set() { - s.Logs = viper.GetBool("logs") - s.Debug = viper.GetBool("debug") s.Config = viper.GetString("config") + + logLevel := viper.GetString("log.level") + level, err := zerolog.ParseLevel(logLevel) + if err != nil { + log.Warn().Msgf("unknown log level %s", logLevel) + } else { + s.LogLevel = level + } + + logTime := viper.GetString("log.time") + switch logTime { + case "unix": + s.LogTime = zerolog.TimeFormatUnix + case "unixms": + s.LogTime = zerolog.TimeFormatUnixMs + case "unixmicro": + s.LogTime = zerolog.TimeFormatUnixMicro + default: + log.Warn().Msgf("unknown log time %s", logTime) + } + + s.LogJson = viper.GetBool("log.json") + s.LogNocolor = viper.GetBool("log.nocolor") + s.LogDir = viper.GetString("log.dir") + + if viper.GetBool("debug") && s.LogLevel != zerolog.TraceLevel { + s.LogLevel = zerolog.DebugLevel + } + + // support for NO_COLOR env variable: https://no-color.org/ + if os.Getenv("NO_COLOR") != "" { + s.LogNocolor = true + } } diff --git a/internal/websocket/manager.go b/internal/websocket/manager.go index f0aa2559..3ec44e40 100644 --- a/internal/websocket/manager.go +++ b/internal/websocket/manager.go @@ -105,7 +105,7 @@ func (manager *WebSocketManagerCtx) Start() { manager.sessions.Broadcast(event.CONTROL_HOST, payload, nil) - manager.logger.Debug(). + manager.logger.Info(). Bool("has_host", payload.HasHost). Str("host_id", payload.HostID). Msg("session host changed") @@ -117,7 +117,7 @@ func (manager *WebSocketManagerCtx) Start() { return } - manager.logger.Debug().Msg("sync clipboard") + manager.logger.Info().Msg("sync clipboard") data, err := manager.desktop.ClipboardGetText() if err != nil {