Skip to content

Commit

Permalink
otellogr: Implement LevelSeverity, Enabled (#6206)
Browse files Browse the repository at this point in the history
This PR implements a few methods for otellogr package
- Configurable LevelSeverity
- Enabled

Part of
#5192

---------

Co-authored-by: Robert Pająk <[email protected]>
  • Loading branch information
scorpionknifes and pellared authored Oct 28, 2024
1 parent f3c51a1 commit 1379d74
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 21 deletions.
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(level int) log.Severity {
switch level {
case 0:
return log.SeverityInfo
case 1:
return log.SeverityDebug
default:
return log.SeverityTrace
}
}),
))
}
71 changes: 57 additions & 14 deletions bridges/otellogr/logsink.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@
// 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 V-level is transformed by using the [WithLevelSeverity] option. If option is
// not provided then V-level is transformed in the following way:
//
// - logr.Info and logr.V(0) are transformed to [log.SeverityInfo].
// - logr.V(1) is transformed to [log.SeverityDebug].
// - logr.V(2) and higher are transformed to [log.SeverityTrace].
//
// KeysAndValues values are transformed based on their type. The following types are
// supported:
//
Expand Down Expand Up @@ -57,6 +65,8 @@ type config struct {
provider log.LoggerProvider
version string
schemaURL string

levelSeverity func(int) log.Severity
}

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

if c.levelSeverity == nil {
c.levelSeverity = func(level int) log.Severity {
switch level {
case 0:
return log.SeverityInfo
case 1:
return log.SeverityDebug
default:
return log.SeverityTrace
}
}
}

return c
}

Expand Down Expand Up @@ -113,6 +136,22 @@ 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 that transforms in the following way:
//
// - logr.Info and logr.V(0) are transformed to [log.SeverityInfo].
// - logr.V(1) is transformed to [log.SeverityDebug].
// - logr.V(2) and higher are transformed to [log.SeverityTrace].
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 @@ -129,10 +168,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 @@ -142,12 +182,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 @@ -157,8 +198,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 @@ -170,7 +213,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
85 changes: 80 additions & 5 deletions bridges/otellogr/logsink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,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 @@ -117,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 @@ -139,6 +142,48 @@ func TestLogSink(t *testing.T) {
},
},
},
{
name: "info_with_level_severity",
f: func(l *logr.Logger) {
l.V(0).Info("msg")
l.V(1).Info("msg")
l.V(2).Info("msg")
l.V(3).Info("msg")
},
wantRecords: map[string][]log.Record{
name: {
buildRecord(log.StringValue("msg"), time.Time{}, log.SeverityInfo, nil),
buildRecord(log.StringValue("msg"), time.Time{}, log.SeverityDebug, nil),
buildRecord(log.StringValue("msg"), time.Time{}, log.SeverityTrace, nil),
buildRecord(log.StringValue("msg"), time.Time{}, log.SeverityTrace, 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 @@ -235,7 +280,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 @@ -260,6 +308,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.SeverityInfo
default:
return log.SeverityDebug
}
}),
)

assert.True(t, ls.Enabled(0))
assert.False(t, ls.Enabled(1))
}

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

0 comments on commit 1379d74

Please sign in to comment.