Created
June 16, 2025 13:06
-
-
Save kriansa/5db352099abebe119b91f380fb0ce617 to your computer and use it in GitHub Desktop.
Simple slog wrapper with good DevX
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package log | |
| import ( | |
| "context" | |
| "log/slog" | |
| "os" | |
| "time" | |
| "github.com/charmbracelet/lipgloss" | |
| "github.com/charmbracelet/log" | |
| "github.com/mattn/go-isatty" | |
| ) | |
| var logger *slog.Logger | |
| type LogFormat string | |
| const ( | |
| LogFormatHuman LogFormat = "human" | |
| LogFormatLogfmt LogFormat = "logfmt" | |
| LogFormatAuto LogFormat = "auto" | |
| ) | |
| func Setup(verbose bool, format LogFormat) { | |
| var handler slog.Handler | |
| if format == LogFormatHuman { | |
| handler = getHumanHandler(verbose) | |
| } else if format == LogFormatLogfmt { | |
| handler = getLogfmtHandler(verbose) | |
| } else { | |
| if format != LogFormatAuto { | |
| Warn("Invalid log format specified, using auto format", "format", format) | |
| } | |
| if isatty.IsTerminal(os.Stdout.Fd()) { | |
| handler = getHumanHandler(verbose) | |
| } else { | |
| handler = getLogfmtHandler(verbose) | |
| } | |
| } | |
| logger = slog.New(handler) | |
| } | |
| func getLogfmtHandler(verbose bool) slog.Handler { | |
| var logLevel slog.Level | |
| if verbose { | |
| logLevel = slog.LevelDebug | |
| } else { | |
| logLevel = slog.LevelInfo | |
| } | |
| return slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel}) | |
| } | |
| func getHumanHandler(verbose bool) slog.Handler { | |
| styles := log.DefaultStyles() | |
| // Set the logfmt tags to be displayed colored, highlighting their output | |
| styles.Key = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("25")) | |
| styles.Value = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("29")) | |
| // Style the different log levels | |
| styles.Levels[log.ErrorLevel] = lipgloss.NewStyle(). | |
| SetString("Error:").Bold(true).Foreground(lipgloss.Color("1")) | |
| styles.Levels[log.WarnLevel] = lipgloss.NewStyle(). | |
| SetString("Warning:").Bold(true).Foreground(lipgloss.Color("3")) | |
| styles.Levels[log.DebugLevel] = lipgloss.NewStyle().SetString("+").Foreground(lipgloss.Color("8")) | |
| // Because most log entries are at Info level, we don't want to display a prefix | |
| delete(styles.Levels, log.InfoLevel) | |
| var logLevel log.Level | |
| if verbose { | |
| logLevel = log.DebugLevel | |
| } else { | |
| logLevel = log.InfoLevel | |
| } | |
| handler := log.NewWithOptions(os.Stdout, log.Options{ | |
| Level: logLevel, | |
| ReportTimestamp: true, | |
| TimeFormat: time.TimeOnly, | |
| }) | |
| handler.SetStyles(styles) | |
| return handler | |
| } | |
| // Wraps the slog.Logger methods to provide a more convenient API. | |
| // Log emits a log record with the current time and the given level and message. | |
| // The Record's Attrs consist of the Logger's attributes followed by | |
| // the Attrs specified by args. | |
| // | |
| // The attribute arguments are processed as follows: | |
| // - If an argument is an Attr, it is used as is. | |
| // - If an argument is a string and this is not the last argument, | |
| // the following argument is treated as the value and the two are combined | |
| // into an Attr. | |
| // - Otherwise, the argument is treated as a value with key "!BADKEY". | |
| func Log(ctx context.Context, level slog.Level, msg string, args ...any) { | |
| logger.Log(ctx, level, msg, args...) | |
| } | |
| // LogAttrs is a more efficient version of [Logger.Log] that accepts only Attrs. | |
| func LogAttrs(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) { | |
| logger.LogAttrs(ctx, level, msg, attrs...) | |
| } | |
| // Debug logs at [LevelDebug]. | |
| func Debug(msg string, args ...any) { | |
| logger.Debug(msg, args...) | |
| } | |
| // DebugContext logs at [LevelDebug] with the given context. | |
| func DebugContext(ctx context.Context, msg string, args ...any) { | |
| logger.DebugContext(ctx, msg, args...) | |
| } | |
| // Info logs at [LevelInfo]. | |
| func Info(msg string, args ...any) { | |
| logger.Info(msg, args...) | |
| } | |
| // InfoContext logs at [LevelInfo] with the given context. | |
| func InfoContext(ctx context.Context, msg string, args ...any) { | |
| logger.InfoContext(ctx, msg, args...) | |
| } | |
| // Warn logs at [LevelWarn]. | |
| func Warn(msg string, args ...any) { | |
| logger.Warn(msg, args...) | |
| } | |
| // WarnContext logs at [LevelWarn] with the given context. | |
| func WarnContext(ctx context.Context, msg string, args ...any) { | |
| logger.WarnContext(ctx, msg, args...) | |
| } | |
| // Error logs at [LevelError]. | |
| func Error(msg string, args ...any) { | |
| logger.Error(msg, args...) | |
| } | |
| // ErrorContext logs at [LevelError] with the given context. | |
| func ErrorContext(ctx context.Context, msg string, args ...any) { | |
| logger.ErrorContext(ctx, msg, args...) | |
| } | |
| // With returns a Logger that includes the given attributes | |
| // in each output operation. Arguments are converted to | |
| // attributes as if by [Logger.Log]. | |
| func With(args ...any) *slog.Logger { | |
| return logger.With(args...) | |
| } | |
| // WithGroup returns a Logger that starts a group, if name is non-empty. | |
| // The keys of all attributes added to the Logger will be qualified by the given | |
| // name. (How that qualification happens depends on the [Handler.WithGroup] | |
| // method of the Logger's Handler.) | |
| // | |
| // If name is empty, WithGroup returns the receiver. | |
| func WithGroup(name string) *slog.Logger { | |
| return logger.WithGroup(name) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment