// Package logger provides structured logging using slog. package logger import ( "fmt" "log/slog" "os" "path/filepath" "runtime" "go.uber.org/fx" "sneak.berlin/go/pixa/internal/globals" ) // Params defines dependencies for Logger. type Params struct { fx.In Globals *globals.Globals } // Logger wraps slog with application-specific functionality. type Logger struct { log *slog.Logger level *slog.LevelVar globals *globals.Globals } // New creates a new Logger instance. func New(_ fx.Lifecycle, params Params) (*Logger, error) { l := &Logger{ level: new(slog.LevelVar), globals: params.Globals, } l.level.Set(slog.LevelInfo) // TTY detection for dev vs prod output tty := false if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) != 0 { tty = true } // replaceAttr simplifies the source attribute to "file.go:line" replaceAttr := func(_ []string, a slog.Attr) slog.Attr { if a.Key == slog.SourceKey { if src, ok := a.Value.Any().(*slog.Source); ok { a.Value = slog.StringValue(fmt.Sprintf("%s:%d", filepath.Base(src.File), src.Line)) } } return a } var handler slog.Handler if tty { // Text output for development handler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: l.level, AddSource: true, ReplaceAttr: replaceAttr, }) } else { // JSON output for production handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ Level: l.level, AddSource: true, ReplaceAttr: replaceAttr, }) } l.log = slog.New(handler) return l, nil } // EnableDebugLogging sets the log level to debug. func (l *Logger) EnableDebugLogging() { l.level.Set(slog.LevelDebug) l.log.Debug("debug logging enabled", "debug", true) } // Get returns the underlying slog.Logger. func (l *Logger) Get() *slog.Logger { return l.log } // Identify logs application startup information. func (l *Logger) Identify() { l.log.Info("starting", "appname", l.globals.Appname, "version", l.globals.Version, "arch", runtime.GOARCH, ) }