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
20 changes: 20 additions & 0 deletions bridges/otelslog/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type config struct {
schemaURL string
attributes []attribute.KeyValue
source bool
level slog.Level
}

func newConfig(options []Option) config {
Expand All @@ -81,6 +82,10 @@ func newConfig(options []Option) config {
c.provider = global.GetLoggerProvider()
}

if c.level == 0 {
c.level = slog.LevelInfo
}

return c
}

Expand Down Expand Up @@ -157,6 +162,16 @@ func WithSource(source bool) Option {
})
}

// WithLevel returns an [Option] that configures the minimum log level for the [Handler].
// Only log records with a level greater than or equal to the specified level will be processed.
// If not provided, the default level is [slog.LevelInfo].
func WithLevel(level slog.Level) Option {
return optFunc(func(c config) config {
c.level = level
return c
})
}

// Handler is an [slog.Handler] that sends all logging records it receives to
// OpenTelemetry. See package documentation for how conversions are made.
type Handler struct {
Expand All @@ -168,6 +183,7 @@ type Handler struct {
logger log.Logger

source bool
level slog.Level
}

// Compile-time check *Handler implements slog.Handler.
Expand All @@ -186,6 +202,7 @@ func NewHandler(name string, options ...Option) *Handler {
return &Handler{
logger: cfg.logger(name),
source: cfg.source,
level: cfg.level,
}
}

Expand Down Expand Up @@ -264,6 +281,9 @@ func (h *Handler) convertRecord(r slog.Record) log.Record {
// Enabled returns true if the Handler is enabled to log for the provided
// context and Level. Otherwise, false is returned if it is not enabled.
func (h *Handler) Enabled(ctx context.Context, l slog.Level) bool {
if l < h.level {
return false
}
const sevOffset = slog.Level(log.SeverityDebug) - slog.LevelDebug
param := log.EnabledParameters{Severity: log.Severity(l + sevOffset)}
return h.logger.Enabled(ctx, param)
Expand Down
43 changes: 42 additions & 1 deletion bridges/otelslog/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ func TestSLogHandler(t *testing.T) {
hasAttr("severityText", "DEBUG"),
hasAttr(slog.MessageKey, "two"),
}},
options: []Option{WithLevel(slog.LevelDebug)},
},
{
name: "multi-attrs",
Expand Down Expand Up @@ -523,7 +524,7 @@ func TestHandlerEnabled(t *testing.T) {
r := new(recorder)
r.MinSeverity = log.SeverityInfo

h := NewHandler("name", WithLoggerProvider(r))
h := NewHandler("name", WithLoggerProvider(r), WithLevel(slog.LevelDebug))

ctx := t.Context()
assert.False(t, h.Enabled(ctx, slog.LevelDebug), "level conversion: permissive")
Expand All @@ -533,6 +534,46 @@ func TestHandlerEnabled(t *testing.T) {
assert.True(t, h.Enabled(ctx, slog.LevelDebug), "context not passed")
}

func TestHandlerLevel(t *testing.T) {
r := new(recorder)
r.MinSeverity = log.SeverityDebug

ctx := t.Context()
t.Run("DefaultLevel", func(t *testing.T) {
h := NewHandler("name", WithLoggerProvider(r))
assert.False(t, h.Enabled(ctx, slog.LevelDebug), "default level should be Info, filtering Debug")
assert.True(t, h.Enabled(ctx, slog.LevelInfo), "Info level should be enabled")
assert.True(t, h.Enabled(ctx, slog.LevelWarn), "Warn level should be enabled")
assert.True(t, h.Enabled(ctx, slog.LevelError), "Error level should be enabled")
})

t.Run("CustomLevel", func(t *testing.T) {
h := NewHandler("name", WithLoggerProvider(r), WithLevel(slog.LevelWarn))
assert.False(t, h.Enabled(ctx, slog.LevelDebug), "Debug should be filtered")
assert.False(t, h.Enabled(ctx, slog.LevelInfo), "Info should be filtered")
assert.True(t, h.Enabled(ctx, slog.LevelWarn), "Warn level should be enabled")
assert.True(t, h.Enabled(ctx, slog.LevelError), "Error level should be enabled")
})

t.Run("DebugLevel", func(t *testing.T) {
h := NewHandler("name", WithLoggerProvider(r), WithLevel(slog.LevelDebug))
assert.True(t, h.Enabled(ctx, slog.LevelDebug), "Debug level should be enabled")
assert.True(t, h.Enabled(ctx, slog.LevelInfo), "Info level should be enabled")
assert.True(t, h.Enabled(ctx, slog.LevelWarn), "Warn level should be enabled")
assert.True(t, h.Enabled(ctx, slog.LevelError), "Error level should be enabled")
})

t.Run("LevelWithContext", func(t *testing.T) {
h := NewHandler("name", WithLoggerProvider(r))
ctx := context.WithValue(context.Background(), enableKey, true)

assert.False(t, h.Enabled(ctx, slog.LevelDebug), "handler level filter applies even with context")

h2 := NewHandler("name", WithLoggerProvider(r), WithLevel(slog.LevelDebug))
assert.True(t, h2.Enabled(ctx, slog.LevelDebug), "context passed when handler allows debug level")
})
}

func TestHandlerErrorFieldSetErr(t *testing.T) {
t.Run("RecordAttr", func(t *testing.T) {
r := new(recorder)
Expand Down