Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed an issue where otellogr emits nil context which leads to panic in exporters #6527

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

- Generate server metrics with semantic conventions v1.26 in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` when `OTEL_SEMCONV_STABILITY_OPT_IN` is set to `http/dup`. (#6411)

### Fixed

- Fixed an issue where otellogr emits nil context which leads to panic in exporters `go.opentelemetry.io/contrib/bridges/otellogr`. (#6527)

## [1.33.0/0.58.0/0.27.0/0.13.0/0.8.0/0.6.0/0.5.0] - 2024-12-12

### Added
Expand Down
41 changes: 34 additions & 7 deletions bridges/otellogr/logsink.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ import (
)

type config struct {
provider log.LoggerProvider
version string
schemaURL string

provider log.LoggerProvider
version string
schemaURL string
ctx context.Context
levelSeverity func(int) log.Severity
}

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

if c.ctx == nil {
c.ctx = context.Background()
}

if c.levelSeverity == nil {
c.levelSeverity = func(level int) log.Severity {
switch level {
Expand Down Expand Up @@ -139,6 +143,18 @@ func WithLoggerProvider(provider log.LoggerProvider) Option {
})
}

// WithContext returns an [Option] that configures the [context.Context] used by
// a [LogSink].
//
// By default if this Option is not provided, the LogSink will use the
// background context.
func WithContext(ctx context.Context) Option {
return optFunc(func(c config) config {
c.ctx = ctx
return c
})
}

// WithLevelSeverity returns an [Option] that configures the function used to
// convert logr levels to OpenTelemetry log severities.
//
Expand Down Expand Up @@ -176,6 +192,7 @@ func NewLogSink(name string, options ...Option) *LogSink {
logger: c.provider.Logger(name, opts...),
levelSeverity: c.levelSeverity,
opts: opts,
ctx: c.ctx,
}
}

Expand All @@ -201,9 +218,8 @@ var _ logr.LogSink = (*LogSink)(nil)
// For example, commandline flags might be used to set the logging
// verbosity and disable some info logs.
func (l *LogSink) Enabled(level int) bool {
ctx := context.Background()
param := log.EnabledParameters{Severity: l.levelSeverity(level)}
return l.logger.Enabled(ctx, param)
return l.logger.Enabled(l.ctx, param)
}

// Error logs an error, with the given message and key/value pairs.
Expand Down Expand Up @@ -238,9 +254,20 @@ func (l *LogSink) Info(level int, msg string, keysAndValues ...any) {
l.logger.Emit(ctx, record)
}

// WithContext returns a new LogSink with the specified context.
func (l *LogSink) WithContext(ctx context.Context) *LogSink {
if ctx == nil {
return l
}
sink := new(LogSink)
*sink = *l
sink.ctx = ctx
return sink
}

// Init receives optional information about the logr library this
// implementation does not use it.
func (l *LogSink) Init(info logr.RuntimeInfo) {
func (l *LogSink) Init(logr.RuntimeInfo) {
// We don't need to do anything here.
// CallDepth is used to calculate the caller's PC.
// PC is dropped as part of the conversion to the OpenTelemetry log.Record.
Expand Down
80 changes: 63 additions & 17 deletions bridges/otellogr/logsink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,46 +28,56 @@ func (mockLoggerProvider) Logger(name string, options ...log.LoggerOption) log.L

func TestNewConfig(t *testing.T) {
customLoggerProvider := mockLoggerProvider{}
ctx := context.WithValue(context.Background(), "key", "value") // nolint:revive,staticcheck

for _, tt := range []struct {
name string
options []Option

wantConfig config
name string
options []Option
wantFunc func(config)
}{
{
name: "with no options",

wantConfig: config{
provider: global.GetLoggerProvider(),
wantFunc: func(c config) {
assert.Equal(t, global.GetLoggerProvider(), c.provider)
},
},
{
name: "with a custom instrumentation scope",
options: []Option{
WithVersion("42.0"),
},

wantConfig: config{
version: "42.0",
provider: global.GetLoggerProvider(),
wantFunc: func(c config) {
assert.Equal(t, "42.0", c.version)
},
},
{
name: "with a custom logger provider",
options: []Option{
WithLoggerProvider(customLoggerProvider),
},

wantConfig: config{
provider: customLoggerProvider,
wantFunc: func(c config) {
assert.Equal(t, customLoggerProvider, c.provider)
},
},
{
name: "default context",
options: []Option{},
wantFunc: func(c config) {
assert.Equal(t, context.Background(), c.ctx)
},
},
{
name: "with a custom context",
options: []Option{
WithContext(ctx),
},
wantFunc: func(c config) {
assert.Equal(t, ctx, c.ctx)
},
},
} {
t.Run(tt.name, func(t *testing.T) {
config := newConfig(tt.options)
config.levelSeverity = nil // Ignore asserting level severity function, assert.Equal does not support function comparison
assert.Equal(t, tt.wantConfig, config)
tt.wantFunc(newConfig(tt.options))
})
}
}
Expand Down Expand Up @@ -375,6 +385,42 @@ func TestLogSinkEnabled(t *testing.T) {
assert.False(t, ls.Enabled(1))
}

func TestLogSinkWithContext(t *testing.T) {
rec := logtest.NewRecorder()
ls := NewLogSink(
"name",
WithLoggerProvider(rec),
)

t.Run("no context", func(t *testing.T) {
defer rec.Reset()
ls.Info(0, "msg")
require.Len(t, rec.Result(), 1)
require.Len(t, rec.Result()[0].Records, 1)
assert.Empty(t, rec.Result()[0].Records[0].Context())
})

t.Run("with nil context", func(t *testing.T) {
defer rec.Reset()

ls2 := ls.WithContext(nil) //nolint:staticcheck
assert.Same(t, ls, ls2)
})

t.Run("with context", func(t *testing.T) {
defer rec.Reset()

ctx := context.WithValue(context.Background(), "key", "value") //nolint:revive,staticcheck
ls2 := ls.WithContext(ctx)
assert.NotSame(t, ls, ls2)

ls2.Info(0, "msg")
require.Len(t, rec.Result(), 1)
require.Len(t, rec.Result()[0].Records, 1)
assert.Equal(t, "value", rec.Result()[0].Records[0].Context().Value("key"))
})
}

func buildRecord(body log.Value, timestamp time.Time, severity log.Severity, attrs []log.KeyValue) log.Record {
var record log.Record
record.SetBody(body)
Expand Down
Loading