Skip to content
Open
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
2 changes: 1 addition & 1 deletion context_slog.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func FromContext(ctx context.Context) (Logger, error) {
case Logger:
return v, nil
case *slog.Logger:
return FromSlogHandler(v.Handler()), nil
return FromSlogHandlerWithContext(ctx, v.Handler()), nil
default:
// Not reached.
panic(fmt.Sprintf("unexpected value type for logr context key: %T", v))
Expand Down
33 changes: 33 additions & 0 deletions context_slog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,36 @@ func TestContextWithSlog(t *testing.T) {

// ...read as logr is covered in the non-slog test
}

func TestContextWithSlogPassesContextToHandler(t *testing.T) {
type ctxKey struct{}

handler := &contextCaptureSlogHandler{Handler: slog.NewJSONHandler(os.Stderr, nil)}
ctx := NewContextWithSlogLogger(context.Background(), slog.New(handler))

ctx = context.WithValue(ctx, ctxKey{}, "value")
logger, err := FromContext(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

logger.Info("hello")

if handler.ctx != ctx {
t.Fatalf("expected slog handler to get original context")
}

if v := handler.ctx.Value(ctxKey{}); v != "value" {
t.Fatalf("expected context value to be preserved, got %#v", v)
}
}

type contextCaptureSlogHandler struct {
slog.Handler
ctx context.Context
}

func (h *contextCaptureSlogHandler) Handle(ctx context.Context, record slog.Record) error {
h.ctx = ctx
return h.Handler.Handle(ctx, record)
}
18 changes: 17 additions & 1 deletion slogr.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,30 @@ import (
//
// The logr verbosity level is mapped to slog levels such that V(0) becomes
// slog.LevelInfo and V(4) becomes slog.LevelDebug.
//
// When you need a short-lived Logger linked to the current context, consider
// using FromSlogHandlerWithContext instead and pass the context to it.
func FromSlogHandler(handler slog.Handler) Logger {
return FromSlogHandlerWithContext(context.Background(), handler)
}

// FromSlogHandlerWithContext returns a Logger which writes to the slog.Handler.
//
// The logr verbosity level is mapped to slog levels such that V(0) becomes
// slog.LevelInfo and V(4) becomes slog.LevelDebug.
//
// The provided context is passed to the slog.Handler when logging. This allows
// the slog.Handler to use the context. This should only be used for short-lived
Copy link

@obarisk obarisk Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the following a little more descriptive?

an slog which following the lifecycle of the context

not limit to the vague long lived v.s. short lived

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you provide a diff? I would like to understand exactly what you would like to change, as I think that the current comment is quite clear already.

// Loggers that do not outlive the context. If you need a long-lived Logger, use
// FromSlogHandler instead.
func FromSlogHandlerWithContext(ctx context.Context, handler slog.Handler) Logger {
if handler, ok := handler.(*slogHandler); ok {
if handler.sink == nil {
return Discard()
}
return New(handler.sink).V(int(handler.levelBias))
}
return New(&slogSink{handler: handler})
return New(&slogSink{handler: handler, ctx: ctx})
}

// ToSlogHandler returns a slog.Handler which writes to the same sink as the Logger.
Expand Down
5 changes: 3 additions & 2 deletions slogsink.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type slogSink struct {
callDepth int
name string
handler slog.Handler
ctx context.Context
}

func (l *slogSink) Init(info RuntimeInfo) {
Expand All @@ -66,7 +67,7 @@ func (l *slogSink) WithCallDepth(depth int) LogSink {
}

func (l *slogSink) Enabled(level int) bool {
return l.handler.Enabled(context.Background(), slog.Level(-level))
return l.handler.Enabled(l.ctx, slog.Level(-level))
}

func (l *slogSink) Info(level int, msg string, kvList ...interface{}) {
Expand All @@ -90,7 +91,7 @@ func (l *slogSink) log(err error, msg string, level slog.Level, kvList ...interf
record.AddAttrs(slog.Any(errKey, err))
}
record.Add(kvList...)
_ = l.handler.Handle(context.Background(), record)
_ = l.handler.Handle(l.ctx, record)
}

func (l slogSink) WithName(name string) LogSink {
Expand Down