From 29b6fc1dcdbffc427296271f34b36843e516b036 Mon Sep 17 00:00:00 2001 From: Joshua Rich Date: Fri, 4 Oct 2024 14:55:18 +1000 Subject: [PATCH] refactor(logging): :recycle: further logging improvements - use slogtint for console and regular slog for log file - additional colorisation for console --- internal/logging/logging.go | 71 +++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 19 deletions(-) diff --git a/internal/logging/logging.go b/internal/logging/logging.go index 9ca7cb173..5f94a93de 100644 --- a/internal/logging/logging.go +++ b/internal/logging/logging.go @@ -12,6 +12,7 @@ import ( "log/slog" "os" "path/filepath" + "time" slogmulti "github.com/samber/slog-multi" @@ -44,7 +45,7 @@ func New(appID string, options Options) *slog.Logger { var ( logLevel slog.Level logFile string - handler slog.Handler + handlers []slog.Handler ) // Set the log level. @@ -64,7 +65,9 @@ func New(appID string, options Options) *slog.Logger { logFile = filepath.Join(xdg.ConfigHome, appID, logFileName) } - // Set the slog handler + handlers = append(handlers, tint.NewHandler(os.Stderr, + generateConsoleOptions(logLevel, os.Stderr.Fd()))) + // Unless no log file was requested, set up file logging. if logFile != "" { logFH, err := openLogFile(logFile) @@ -73,27 +76,34 @@ func New(appID string, options Options) *slog.Logger { slog.String("file", logFile), slog.Any("error", err)) } else { - handler = slogmulti.Fanout( - tint.NewHandler(os.Stdout, generateOptions(logLevel, os.Stdout.Fd())), - tint.NewHandler(logFH, generateOptions(logLevel, logFH.Fd())), - ) + handlers = append(handlers, slog.NewTextHandler(logFH, generateFileOpts(logLevel))) } - } else { - handler = slogmulti.Fanout( - tint.NewHandler(os.Stdout, generateOptions(logLevel, os.Stdout.Fd())), - ) } - logger := slog.New(handler) + logger := slog.New(slogmulti.Fanout(handlers...)) slog.SetDefault(logger) return logger } -func generateOptions(level slog.Level, fd uintptr) *tint.Options { +func generateConsoleOptions(level slog.Level, fd uintptr) *tint.Options { opts := &tint.Options{ - Level: level, - NoColor: !isatty.IsTerminal(fd), + Level: level, + NoColor: !isatty.IsTerminal(fd), + ReplaceAttr: tintLevelReplacer, + TimeFormat: time.Kitchen, + } + if level == LevelTrace { + opts.AddSource = true + } + + return opts +} + +func generateFileOpts(level slog.Level) *slog.HandlerOptions { + opts := &slog.HandlerOptions{ + Level: level, + ReplaceAttr: fileLevelReplacer, } if level == LevelTrace { opts.AddSource = true @@ -102,20 +112,43 @@ func generateOptions(level slog.Level, fd uintptr) *tint.Options { return opts } -//nolint:unused -func levelReplacer(_ []string, attr slog.Attr) slog.Attr { +func tintLevelReplacer(_ []string, attr slog.Attr) slog.Attr { + // Set default level. if attr.Key == slog.LevelKey { level, ok := attr.Value.Any().(slog.Level) if !ok { level = slog.LevelInfo } + // Errors in red. + if err, ok := attr.Value.Any().(error); ok { + aErr := tint.Err(err) + attr.Key = aErr.Key + } + + // Format custom log level. levelLabel, exists := LevelNames[level] - if !exists { - levelLabel = level.String() + if exists { + attr.Value = slog.StringValue(levelLabel) + } + } + + return attr +} + +func fileLevelReplacer(_ []string, attr slog.Attr) slog.Attr { + // Set default level. + if attr.Key == slog.LevelKey { + level, ok := attr.Value.Any().(slog.Level) + if !ok { + level = slog.LevelInfo } - attr.Value = slog.StringValue(levelLabel) + // Format custom log level. + levelLabel, exists := LevelNames[level] + if exists { + attr.Value = slog.StringValue(levelLabel) + } } return attr