package logging import ( "context" "log/slog" "net/http" "os" "time" ) type logKeyType struct{} var logKey = logKeyType{} func FromReq(r *http.Request) *slog.Logger { return FromCtx(r.Context()) } func ToReq(r *http.Request, newLog *slog.Logger) *http.Request { return r.WithContext(context.WithValue(r.Context(), logKey, newLog)) } func FromCtx(c context.Context) *slog.Logger { val := c.Value(logKey) log, ok := val.(*slog.Logger) if !ok { log = New() log.Warn("Log not configured. Likely a code error.") } return log } func replaceLogAttrs(groups []string, a slog.Attr) slog.Attr { if a.Key == slog.TimeKey && len(groups) == 0 { return slog.Int64("at", a.Value.Any().(time.Time).UnixMilli()) } if a.Key == slog.LevelKey && len(groups) == 0 { lvl := a.Value.Any().(slog.Level) lvlStr := lvl.String() switch lvl { case slog.LevelDebug: lvlStr = "D" case slog.LevelInfo: lvlStr = "I" case slog.LevelWarn: lvlStr = "W" case slog.LevelError: lvlStr = "E" } return slog.String("lvl", lvlStr) } return a } func New() *slog.Logger { logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: replaceLogAttrs})) return logger } func InjectHttp(l *slog.Logger, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { next.ServeHTTP(w, ToReq(r, l)) }) }