diff --git a/bridges/otelzap/core.go b/bridges/otelzap/core.go index 82680c233ba..00396681555 100644 --- a/bridges/otelzap/core.go +++ b/bridges/otelzap/core.go @@ -7,6 +7,7 @@ package otelzap // import "go.opentelemetry.io/contrib/bridges/otelzap" import ( "context" + "slices" "go.uber.org/zap/zapcore" @@ -88,6 +89,7 @@ func WithLoggerProvider(provider log.LoggerProvider) Option { // Core is a [zapcore.Core] that sends logging records to OpenTelemetry. type Core struct { logger log.Logger + attr []log.KeyValue } // Compile-time check *Core implements zapcore.Core. @@ -108,10 +110,20 @@ func (o *Core) Enabled(level zapcore.Level) bool { return o.logger.Enabled(context.Background(), r) } -// TODO // With adds structured context to the Core. func (o *Core) With(fields []zapcore.Field) zapcore.Core { - return o + cloned := o.clone() + if len(fields) > 0 { + cloned.attr = append(cloned.attr, convertField(fields)...) + } + return cloned +} + +func (o *Core) clone() *Core { + return &Core{ + logger: o.logger, + attr: slices.Clone(o.attr), + } } // TODO @@ -143,9 +155,9 @@ func (o *Core) Write(ent zapcore.Entry, fields []zapcore.Field) error { // TODO: Handle zap.Array. // TODO: Handle ent.LoggerName. + r.AddAttributes(o.attr...) if len(fields) > 0 { - attrbuf := convertField(fields) - r.AddAttributes(attrbuf...) + r.AddAttributes(convertField(fields)...) } o.logger.Emit(context.Background(), r) diff --git a/bridges/otelzap/core_test.go b/bridges/otelzap/core_test.go index 1d7b519cce6..82e6a23b8a6 100644 --- a/bridges/otelzap/core_test.go +++ b/bridges/otelzap/core_test.go @@ -29,16 +29,61 @@ func TestCore(t *testing.T) { zc := NewCore(loggerName, WithLoggerProvider(rec)) logger := zap.New(zc) - logger.Info(testMessage, zap.String(testKey, testValue)) + t.Run("Write", func(t *testing.T) { + logger.Info(testMessage, zap.String(testKey, testValue)) + got := rec.Result()[0].Records[0] + assert.Equal(t, testMessage, got.Body().AsString()) + assert.Equal(t, log.SeverityInfo, got.Severity()) + assert.Equal(t, 1, got.AttributesLen()) + got.WalkAttributes(func(kv log.KeyValue) bool { + assert.Equal(t, testKey, string(kv.Key)) + assert.Equal(t, testValue, value2Result(kv.Value)) + return true + }) + }) - got := rec.Result()[0].Records[0] - assert.Equal(t, testMessage, got.Body().AsString()) - assert.Equal(t, log.SeverityInfo, got.Severity()) - assert.Equal(t, 1, got.AttributesLen()) - got.WalkAttributes(func(kv log.KeyValue) bool { - assert.Equal(t, testKey, string(kv.Key)) - assert.Equal(t, testValue, value2Result(kv.Value)) - return true + rec.Reset() + + // test child logger with accumulated fields + t.Run("With", func(t *testing.T) { + testCases := [][]string{{"test1", "value1"}, {"test2", "value2"}} + childlogger := logger.With(zap.String(testCases[0][0], testCases[0][1])) + childlogger.Info(testMessage, zap.String(testCases[1][0], testCases[1][1])) + + got := rec.Result()[0].Records[0] + assert.Equal(t, testMessage, got.Body().AsString()) + assert.Equal(t, log.SeverityInfo, got.Severity()) + assert.Equal(t, 2, got.AttributesLen()) + + index := 0 + got.WalkAttributes(func(kv log.KeyValue) bool { + assert.Equal(t, testCases[index][0], string(kv.Key)) + assert.Equal(t, testCases[index][1], value2Result(kv.Value)) + index++ + return true + }) + }) + + rec.Reset() + + t.Run("WithMultiple", func(t *testing.T) { + testCases := [][]string{{"test1", "value1"}, {"test2", "value2"}, {"test3", "value3"}} + childlogger := logger.With(zap.String(testCases[0][0], testCases[0][1])) + childlogger2 := childlogger.With(zap.String(testCases[1][0], testCases[1][1])) + childlogger2.Info(testMessage, zap.String(testCases[2][0], testCases[2][1])) + + got := rec.Result()[0].Records[0] + assert.Equal(t, testMessage, got.Body().AsString()) + assert.Equal(t, log.SeverityInfo, got.Severity()) + assert.Equal(t, 3, got.AttributesLen()) + + index := 0 + got.WalkAttributes(func(kv log.KeyValue) bool { + assert.Equal(t, testCases[index][0], string(kv.Key)) + assert.Equal(t, testCases[index][1], value2Result(kv.Value)) + index++ + return true + }) }) }