package rlog import ( "context" "log/slog" "os" "runtime" "strings" "time" "github.com/rs/zerolog" slogmulti "github.com/samber/slog-multi" slogzerolog "github.com/samber/slog-zerolog" ) var ( zl = zerolog.New(&zerolog.ConsoleWriter{Out: os.Stderr}) handlers = []slog.Handler{ slogzerolog.Option{Logger: &zl}.NewZerologHandler(), } handler = slogmulti.Fanout(handlers...) defaultLogger = slog.New(handler) ) func init() { slog.SetDefault(defaultLogger) } func AddHandler(nh slog.Handler) { handlers = append(handlers, nh) handler = slogmulti.Fanout(handlers...) defaultLogger = slog.New(handler) slog.SetDefault(defaultLogger) } type Logger struct { handler slog.Handler component []string } const functionKey = "function" func (l *Logger) log(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) { var pcs [1]uintptr runtime.Callers(3, pcs[:]) pc := pcs[0] f := runtime.FuncForPC(pc) attrs = append(attrs, slog.String(functionKey, f.Name())) r := slog.NewRecord(time.Now(), level, msg, pc) r.AddAttrs(attrs...) if ctx == nil { ctx = context.Background() } _ = l.handler.Handle(ctx, r) } func (l *Logger) Debug(ctx context.Context, msg string, attrs ...slog.Attr) { l.log(ctx, slog.LevelDebug, msg, attrs...) } func (l *Logger) Info(ctx context.Context, msg string, attrs ...slog.Attr) { l.log(ctx, slog.LevelInfo, msg, attrs...) } func (l *Logger) Warn(ctx context.Context, msg string, attrs ...slog.Attr) { l.log(ctx, slog.LevelWarn, msg, attrs...) } func (l *Logger) Error(ctx context.Context, msg string, attrs ...slog.Attr) { l.log(ctx, slog.LevelError, msg, attrs...) } const componentKey = "component" const componentSep = "." func (log *Logger) WithComponent(name string) *Logger { c := append(log.component, name) return &Logger{ handler: log.handler.WithAttrs([]slog.Attr{ slog.String(componentKey, strings.Join(c, componentSep)), }), component: c, } } func (l *Logger) With(attrs ...slog.Attr) *Logger { return &Logger{ handler: l.handler.WithAttrs(attrs), component: l.component, } } // returns a new slog logger with the same attribures as the original logger // TODO currently not logging function name func (l *Logger) Slog() *slog.Logger { return slog.New(l.handler) } const endpointKey = "endpoint" func (l *Logger) WithEndpoint(name string) *Logger { return &Logger{ handler: l.handler.WithAttrs([]slog.Attr{ slog.String(endpointKey, name), }), component: l.component, } } const errKey = "error" func Error(err error) slog.Attr { return slog.Attr{Key: errKey, Value: errValue(err)} } // errValue returns a slog.GroupValue with keys "msg" and "trace". If the error // does not implement interface { StackTrace() errors.StackTrace }, the "trace" // key is omitted. func errValue(err error) slog.Value { if err == nil { return slog.AnyValue(nil) } var groupValues []slog.Attr groupValues = append(groupValues, slog.String("msg", err.Error()), slog.Any("value", err), ) return slog.GroupValue(groupValues...) } func Component(name ...string) *Logger { return &Logger{ handler: handler, component: name, } }