error-pages/internal/logger/logger.go
2024-07-03 18:12:13 +04:00

127 lines
3.5 KiB
Go

package logger
import (
"context"
"errors"
"io"
"log/slog"
"os"
"strings"
"time"
)
// internalAttrKeyLoggerName is used to store the logger name in the logger context (attributes).
const internalAttrKeyLoggerName = "named_logger"
var (
// consoleFormatAttrReplacer is a replacer for console format. It replaces some attributes with more
// human-readable ones.
consoleFormatAttrReplacer = func(_ []string, a slog.Attr) slog.Attr { //nolint:gochecknoglobals
switch a.Key {
case internalAttrKeyLoggerName:
return slog.String("logger", a.Value.String())
case "level":
return slog.String(a.Key, strings.ToLower(a.Value.String()))
default:
if ts, ok := a.Value.Any().(time.Time); ok && a.Key == "time" {
return slog.String(a.Key, ts.Format("15:04:05"))
}
}
return a
}
// jsonFormatAttrReplacer is a replacer for JSON format. It replaces some attributes with more
// machine-readable ones.
jsonFormatAttrReplacer = func(_ []string, a slog.Attr) slog.Attr { //nolint:gochecknoglobals
switch a.Key {
case internalAttrKeyLoggerName:
return slog.String("logger", a.Value.String())
case "level":
return slog.String(a.Key, strings.ToLower(a.Value.String()))
default:
if ts, ok := a.Value.Any().(time.Time); ok && a.Key == "time" {
return slog.Float64("ts", float64(ts.Unix())+float64(ts.Nanosecond())/1e9)
}
}
return a
}
)
// Logger is a simple logger that wraps [slog.Logger]. It provides a more convenient API for logging and
// formatting messages.
type Logger struct {
ctx context.Context
slog *slog.Logger
lvl Level
}
// New creates a new logger with the given level and format. Optionally, you can specify the writer to write logs to.
func New(l Level, f Format, writer ...io.Writer) (*Logger, error) {
var options slog.HandlerOptions
switch l {
case DebugLevel:
options.Level = slog.LevelDebug
case InfoLevel:
options.Level = slog.LevelInfo
case WarnLevel:
options.Level = slog.LevelWarn
case ErrorLevel:
options.Level = slog.LevelError
default:
return nil, errors.New("unsupported logging level")
}
var (
handler slog.Handler
target io.Writer
)
if len(writer) > 0 && writer[0] != nil {
target = writer[0]
} else {
target = os.Stderr
}
switch f {
case ConsoleFormat:
options.ReplaceAttr = consoleFormatAttrReplacer
handler = slog.NewTextHandler(target, &options)
case JSONFormat:
options.ReplaceAttr = jsonFormatAttrReplacer
handler = slog.NewJSONHandler(target, &options)
default:
return nil, errors.New("unsupported logging format")
}
return &Logger{ctx: context.Background(), slog: slog.New(handler), lvl: l}, nil
}
// Level returns the logger level.
func (l *Logger) Level() Level { return l.lvl }
// Named creates a new logger with the same properties as the original logger and the given name.
func (l *Logger) Named(name string) *Logger {
return &Logger{
ctx: l.ctx,
slog: l.slog.With(slog.String(internalAttrKeyLoggerName, name)),
lvl: l.lvl,
}
}
// Debug logs a message at DebugLevel.
func (l *Logger) Debug(msg string, f ...Attr) { l.slog.LogAttrs(l.ctx, slog.LevelDebug, msg, f...) }
// Info logs a message at InfoLevel.
func (l *Logger) Info(msg string, f ...Attr) { l.slog.LogAttrs(l.ctx, slog.LevelInfo, msg, f...) }
// Warn logs a message at WarnLevel.
func (l *Logger) Warn(msg string, f ...Attr) { l.slog.LogAttrs(l.ctx, slog.LevelWarn, msg, f...) }
// Error logs a message at ErrorLevel.
func (l *Logger) Error(msg string, f ...Attr) { l.slog.LogAttrs(l.ctx, slog.LevelError, msg, f...) }