Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 72 additions & 106 deletions default.go
Original file line number Diff line number Diff line change
@@ -1,160 +1,126 @@
package logger

import (
"context"
"fmt"
"io"
"log/slog"
"os"
"sync"
)

// fieldLogger is a Logger implementation backed by log/slog
type fieldLogger struct {
logger *slog.Logger
attrs []slog.Attr
}

// SetOutput sets the output writer for the logger.
func (s *fieldLogger) SetOutput(w io.Writer) {
opts := &slog.HandlerOptions{}
s.logger = slog.New(slog.NewTextHandler(w, opts))
}

func (s *fieldLogger) WithField(key string, value any) FieldLogger {
return s.WithFields(map[string]any{key: value})
}

func (s *fieldLogger) WithFields(m map[string]any) FieldLogger {
attrs := make([]slog.Attr, 0, len(m)+len(s.attrs))
attrs = append(attrs, s.attrs...)
for k, v := range m {
attrs = append(attrs, slog.Any(k, v))
}
return &fieldLogger{
logger: s.logger,
attrs: attrs,
}
}
var (
defaultLogger FieldLogger
defaultLoggerOnce sync.Once
)

func (s *fieldLogger) log(level slog.Level, msg string, args ...any) {
if len(args) > 0 {
msg = fmt.Sprintf(msg, args...)
// NewLogger based on the specified log level, defaults to "debug".
// See `New` for more details.
func NewLogger(level string) FieldLogger {
lvl, err := ParseLevel(level)
if err != nil {
lvl = DebugLevel
}
s.logger.Log(context.Background(), level, msg, s.attrsToAny()...)
return New(lvl)
}

func (s *fieldLogger) attrsToAny() []any {
result := make([]any, len(s.attrs))
for i, a := range s.attrs {
result[i] = a
}
return result
// Default returns the default FieldLogger instance.
// The default logger is initialized with InfoLevel on first call.
func Default() FieldLogger {
defaultLoggerOnce.Do(func() {
if defaultLogger == nil {
defaultLogger = New(InfoLevel)
}
})
return defaultLogger
}

func (s *fieldLogger) Debugf(msg string, args ...any) {
s.log(slog.LevelDebug, msg, args...)
// SetDefault sets the default logger instance.
// Subsequent calls to Default() and package-level logging functions will use this logger.
func SetDefault(l FieldLogger) {
defaultLogger = l
}

func (s *fieldLogger) Infof(msg string, args ...any) {
s.log(slog.LevelInfo, msg, args...)
// Debugf logs a formatted debug message using the default logger.
func Debugf(msg string, args ...any) {
Default().Debugf(msg, args...)
}

func (s *fieldLogger) Printf(msg string, args ...any) {
s.Infof(msg, args...)
// Infof logs a formatted info message using the default logger.
func Infof(msg string, args ...any) {
Default().Infof(msg, args...)
}

func (s *fieldLogger) Warnf(msg string, args ...any) {
s.log(slog.LevelWarn, msg, args...)
// Printf logs a formatted message using the default logger.
func Printf(msg string, args ...any) {
Default().Printf(msg, args...)
}

func (s *fieldLogger) Errorf(msg string, args ...any) {
s.log(slog.LevelError, msg, args...)
// Warnf logs a formatted warning message using the default logger.
func Warnf(msg string, args ...any) {
Default().Warnf(msg, args...)
}

func (s *fieldLogger) Fatalf(msg string, args ...any) {
s.log(slog.Level(12), msg, args...)
os.Exit(1)
// Errorf logs a formatted error message using the default logger.
func Errorf(msg string, args ...any) {
Default().Errorf(msg, args...)
}

func (s *fieldLogger) Debug(args ...any) {
s.log(slog.LevelDebug, fmt.Sprint(args...))
// Fatalf logs a formatted fatal message using the default logger and exits the program.
func Fatalf(msg string, args ...any) {
Default().Fatalf(msg, args...)
}

func (s *fieldLogger) Info(args ...any) {
s.log(slog.LevelInfo, fmt.Sprint(args...))
// Debug logs a debug message using the default logger.
func Debug(args ...any) {
Default().Debug(args...)
}

func (s *fieldLogger) Warn(args ...any) {
s.log(slog.LevelWarn, fmt.Sprint(args...))
// Info logs an info message using the default logger.
func Info(args ...any) {
Default().Info(args...)
}

func (s *fieldLogger) Error(args ...any) {
s.log(slog.LevelError, fmt.Sprint(args...))
// Warn logs a warning message using the default logger.
func Warn(args ...any) {
Default().Warn(args...)
}

func (s *fieldLogger) Fatal(args ...any) {
s.log(slog.Level(12), fmt.Sprint(args...))
os.Exit(1)
// Error logs an error message using the default logger.
func Error(args ...any) {
Default().Error(args...)
}

func (s *fieldLogger) Panic(args ...any) {
msg := fmt.Sprint(args...)
s.log(slog.Level(12), msg)
panic(msg)
// Fatal logs a fatal message using the default logger and exits the program.
func Fatal(args ...any) {
Default().Fatal(args...)
}

// ParseLevel parses a string level into a Level.
func ParseLevel(level string) (Level, error) {
switch level {
case "panic":
return PanicLevel, nil
case "fatal":
return FatalLevel, nil
case "error":
return ErrorLevel, nil
case "warn", "warning":
return WarnLevel, nil
case "info":
return InfoLevel, nil
case "debug":
return DebugLevel, nil
}

var l Level
return l, fmt.Errorf("not a valid Level: %q", level)
// Panic logs a panic message using the default logger and panics.
func Panic(args ...any) {
Default().Panic(args...)
}

// NewLogger based on the specified log level, defaults to "debug".
// See `New` for more details.
func NewLogger(level string) FieldLogger {
lvl, err := ParseLevel(level)
if err != nil {
lvl = DebugLevel
}
return New(lvl)
// WithField adds a field to the default logger and returns a new FieldLogger.
func WithField(key string, value any) FieldLogger {
return Default().WithField(key, value)
}

// New based on the specified log level, defaults to "debug".
// This logger will log to the STDOUT in a human readable,
// but parseable form.
//
// Example: time="2016-12-01T21:02:07-05:00" level=info duration=225.283µs human_size="106 B" method=GET path="/" render=199.79µs request_id=2265736089 size=106 status=200
func New(lvl Level) FieldLogger {
return newSlog(lvl)
// WithFields adds multiple fields to the default logger and returns a new FieldLogger.
func WithFields(fields map[string]any) FieldLogger {
return Default().WithFields(fields)
}

func newSlog(lvl Level) FieldLogger {
func newDefault(lvl Level) FieldLogger {
opts := &slog.HandlerOptions{
Level: toSlogLevel(lvl),
Level: toDefaultLevel(lvl),
}
handler := slog.NewTextHandler(os.Stdout, opts)
return &fieldLogger{
logger: slog.New(handler),
level: lvl,
}
}

// toSlogLevel converts our Level to slog.Level.
func toSlogLevel(l Level) slog.Level {
// toDefaultLevel converts our Level to slog.Level.
func toDefaultLevel(l Level) slog.Level {
switch l {
case PanicLevel, FatalLevel:
return slog.Level(12) // custom level above error
Expand Down
Loading
Loading