diff --git a/bridges/otellogr/logsink.go b/bridges/otellogr/logsink.go index 4e36fe18e23..1a23efb88c8 100644 --- a/bridges/otellogr/logsink.go +++ b/bridges/otellogr/logsink.go @@ -61,6 +61,11 @@ import ( "go.opentelemetry.io/otel/log/global" ) +const ( + // exceptionMessageKey is the key used for the error message. + exceptionMessageKey = "exception.message" +) + type config struct { provider log.LoggerProvider version string @@ -206,7 +211,23 @@ func (l *LogSink) Enabled(level int) bool { // Error logs an error, with the given message and key/value pairs. func (l *LogSink) Error(err error, msg string, keysAndValues ...any) { - // TODO + var record log.Record + record.SetBody(log.StringValue(msg)) + record.SetSeverity(log.SeverityError) + + record.AddAttributes( + log.KeyValue{ + Key: exceptionMessageKey, + Value: convertValue(err), + }, + ) + + record.AddAttributes(l.attr...) + + ctx, attr := convertKVs(l.ctx, keysAndValues...) + record.AddAttributes(attr...) + + l.logger.Emit(ctx, record) } // Info logs a non-error message with the given key/value pairs. @@ -223,9 +244,12 @@ func (l *LogSink) Info(level int, msg string, keysAndValues ...any) { l.logger.Emit(ctx, record) } -// Init initializes the LogSink. +// Init receives optional information about the logr library this +// implementation does not use it. func (l *LogSink) Init(info logr.RuntimeInfo) { - // TODO + // 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. } // WithName returns a new LogSink with the specified name appended. diff --git a/bridges/otellogr/logsink_test.go b/bridges/otellogr/logsink_test.go index b63a201c412..236ad3b61e6 100644 --- a/bridges/otellogr/logsink_test.go +++ b/bridges/otellogr/logsink_test.go @@ -4,6 +4,7 @@ package otellogr import ( "context" + "errors" "testing" "time" @@ -277,6 +278,49 @@ func TestLogSink(t *testing.T) { }, }, }, + { + name: "error", + f: func(l *logr.Logger) { + l.Error(errors.New("test"), "error message") + }, + wantRecords: map[string][]log.Record{ + name: { + buildRecord(log.StringValue("error message"), time.Time{}, log.SeverityError, []log.KeyValue{ + {Key: exceptionMessageKey, Value: log.StringValue("test")}, + }), + }, + }, + }, + { + name: "error_multi_attrs", + f: func(l *logr.Logger) { + l.Error(errors.New("test error"), "msg", + "struct", struct{ data int64 }{data: 1}, + "bool", true, + "duration", time.Minute, + "float64", 3.14159, + "int64", -2, + "string", "str", + "time", time.Unix(1000, 1000), + "uint64", uint64(3), + ) + }, + wantRecords: map[string][]log.Record{ + name: { + buildRecord(log.StringValue("msg"), time.Time{}, log.SeverityError, []log.KeyValue{ + {Key: exceptionMessageKey, Value: log.StringValue("test error")}, + log.String("struct", "{data:1}"), + log.Bool("bool", true), + log.Int64("duration", 60_000_000_000), + log.Float64("float64", 3.14159), + log.Int64("int64", -2), + log.String("string", "str"), + log.Int64("time", time.Unix(1000, 1000).UnixNano()), + log.Int64("uint64", 3), + }), + }, + }, + }, } { t.Run(tt.name, func(t *testing.T) { rec := logtest.NewRecorder()