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

otellogr: Implement LevelSeverity, Enabled #6206

Merged
16 changes: 14 additions & 2 deletions bridges/otellogr/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/go-logr/logr"

"go.opentelemetry.io/contrib/bridges/otellogr"
"go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/log/noop"
)

Expand All @@ -17,6 +18,17 @@ func Example() {
// Create an logr.Logger with *otellogr.LogSink and use it in your application.
logr.New(otellogr.NewLogSink(
"my/pkg/name",
otellogr.WithLoggerProvider(provider)),
)
otellogr.WithLoggerProvider(provider),
// Optionally, set the log level severity mapping.
otellogr.WithLevelSeverity(func(i int) log.Severity {
dmathieu marked this conversation as resolved.
Show resolved Hide resolved
switch i {
case 0:
return log.SeverityInfo
case 1:
return log.SeverityWarn
default:
return log.SeverityFatal
}
}),
))
}
68 changes: 54 additions & 14 deletions bridges/otellogr/logsink.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,26 @@
// way:
//
// - Message is set as the Body using a [log.StringValue].
// - TODO: Level
// - Level is transformed and set as the Severity. The SeverityText is not
// set.
// - KeyAndValues are transformed and set as Attributes.
// - The [context.Context] value in KeyAndValues is propagated to OpenTelemetry
// log record. All non-nested [context.Context] values are ignored and not
// added as attributes. If there are multiple [context.Context] the last one
// is used.
//
// The Level is transformed by using the [WithLevelSeverity] option. If this is
// not provided it would default to a function that adds an offset to the logr
// such that [logr.Info] is transformed to [log.SeverityInfo]. For example:
//
// - [logr.Info] is transformed to [log.SeverityInfo].
// - [logr.V(0)] is transformed to [log.SeverityInfo].
// - [logr.V(1)] is transformed to [log.SeverityInfo2].
// - [logr.V(2)] is transformed to [log.SeverityInfo3].
// - ...
// - [logr.V(15)] is transformed to [log.SeverityFatal4].
// - [logr.Error] is transformed to [log.SeverityError].
scorpionknifes marked this conversation as resolved.
Show resolved Hide resolved
//
// KeysAndValues values are transformed based on their type. The following types are
// supported:
//
Expand Down Expand Up @@ -56,6 +69,8 @@ type config struct {
provider log.LoggerProvider
version string
schemaURL string

levelSeverity func(int) log.Severity
}

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

if c.levelSeverity == nil {
c.levelSeverity = func(level int) log.Severity {
const sevOffset = int(log.SeverityInfo)
return log.Severity(level + sevOffset)
}
}

return c
}

Expand Down Expand Up @@ -112,6 +134,20 @@ func WithLoggerProvider(provider log.LoggerProvider) Option {
})
}

// WithLevelSeverity returns an [Option] that configures the function used to
// convert logr levels to OpenTelemetry log severities.
//
// By default if this Option is not provided, the LogSink will use a default
// conversion function which adds an offset to the logr level to get the
// OpenTelemetry severity. The offset is such that logr.Info("message")
// converts to OpenTelemetry [log.SeverityInfo].
func WithLevelSeverity(f func(int) log.Severity) Option {
return optFunc(func(c config) config {
c.levelSeverity = f
return c
})
}

// NewLogSink returns a new [LogSink] to be used as a [logr.LogSink].
//
// If [WithLoggerProvider] is not provided, the returned [LogSink] will use the
Expand All @@ -128,10 +164,11 @@ func NewLogSink(name string, options ...Option) *LogSink {
}

return &LogSink{
name: name,
provider: c.provider,
logger: c.provider.Logger(name, opts...),
opts: opts,
name: name,
provider: c.provider,
logger: c.provider.Logger(name, opts...),
levelSeverity: c.levelSeverity,
opts: opts,
}
}

Expand All @@ -141,12 +178,13 @@ type LogSink struct {
// Ensure forward compatibility by explicitly making this not comparable.
noCmp [0]func() //nolint: unused // This is indeed used.

name string
provider log.LoggerProvider
logger log.Logger
opts []log.LoggerOption
attr []log.KeyValue
ctx context.Context
name string
provider log.LoggerProvider
logger log.Logger
levelSeverity func(int) log.Severity
opts []log.LoggerOption
attr []log.KeyValue
ctx context.Context
}

// Compile-time check *Handler implements logr.LogSink.
Expand All @@ -156,8 +194,10 @@ 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 {
// TODO
return true
var param log.EnabledParameters
param.SetSeverity(l.levelSeverity(level))
ctx := context.Background()
return l.logger.Enabled(ctx, param)
}

// Error logs an error, with the given message and key/value pairs.
Expand All @@ -169,7 +209,7 @@ func (l *LogSink) Error(err error, msg string, keysAndValues ...any) {
func (l *LogSink) Info(level int, msg string, keysAndValues ...any) {
var record log.Record
record.SetBody(log.StringValue(msg))
record.SetSeverity(log.SeverityInfo) // TODO: level
record.SetSeverity(l.levelSeverity(level))

record.AddAttributes(l.attr...)

Expand Down
82 changes: 77 additions & 5 deletions bridges/otellogr/logsink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package otellogr

import (
"context"
"testing"
"time"

Expand Down Expand Up @@ -63,7 +64,9 @@ func TestNewConfig(t *testing.T) {
},
} {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.wantConfig, newConfig(tt.options))
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)
})
}
}
Expand Down Expand Up @@ -116,9 +119,10 @@ func TestLogSink(t *testing.T) {
const name = "name"

for _, tt := range []struct {
name string
f func(*logr.Logger)
wantRecords map[string][]log.Record
name string
f func(*logr.Logger)
levelSeverity func(int) log.Severity
wantRecords map[string][]log.Record
}{
{
name: "no_log",
Expand All @@ -138,6 +142,44 @@ func TestLogSink(t *testing.T) {
},
},
},
{
name: "info_with_level_severity",
f: func(l *logr.Logger) {
l.V(1).Info("msg")
l.V(4).Info("msg")
},
wantRecords: map[string][]log.Record{
name: {
buildRecord(log.StringValue("msg"), time.Time{}, log.SeverityInfo2, nil),
buildRecord(log.StringValue("msg"), time.Time{}, log.SeverityWarn, nil),
},
},
},
{
name: "info_with_custom_level_severity",
f: func(l *logr.Logger) {
l.Info("msg")
l.V(1).Info("msg")
l.V(2).Info("msg")
},
levelSeverity: func(level int) log.Severity {
switch level {
case 1:
return log.SeverityError
case 2:
return log.SeverityWarn
default:
return log.SeverityInfo
}
},
wantRecords: map[string][]log.Record{
name: {
buildRecord(log.StringValue("msg"), time.Time{}, log.SeverityInfo, nil),
buildRecord(log.StringValue("msg"), time.Time{}, log.SeverityError, nil),
buildRecord(log.StringValue("msg"), time.Time{}, log.SeverityWarn, nil),
},
},
},
{
name: "info_multi_attrs",
f: func(l *logr.Logger) {
Expand Down Expand Up @@ -219,7 +261,10 @@ func TestLogSink(t *testing.T) {
} {
t.Run(tt.name, func(t *testing.T) {
rec := logtest.NewRecorder()
ls := NewLogSink(name, WithLoggerProvider(rec))
ls := NewLogSink(name,
WithLoggerProvider(rec),
WithLevelSeverity(tt.levelSeverity),
)
l := logr.New(ls)
tt.f(&l)

Expand All @@ -244,6 +289,33 @@ func TestLogSink(t *testing.T) {
}
}

func TestLogSinkEnabled(t *testing.T) {
enabledFunc := func(ctx context.Context, param log.EnabledParameters) bool {
lvl, ok := param.Severity()
if !ok {
return true
}
return lvl == log.SeverityInfo
}

rec := logtest.NewRecorder(logtest.WithEnabledFunc(enabledFunc))
ls := NewLogSink(
"name",
WithLoggerProvider(rec),
WithLevelSeverity(func(i int) log.Severity {
switch i {
case 0:
return log.SeverityDebug
default:
return log.SeverityInfo
}
}),
)

assert.False(t, ls.Enabled(0))
assert.True(t, ls.Enabled(1))
pellared marked this conversation as resolved.
Show resolved Hide resolved
}

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