diff --git a/CHANGELOG.md b/CHANGELOG.md index 239a1b63b0e..06596a192e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - The semantic conventions have been upgraded from `v1.30.0` to `v1.33.0` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#7361) +### Fixed + +- Fix data race when writing log entries with `context.Context` fields in `go.opentelemetry.io/contrib/bridges/otelzap`. (#7368) + diff --git a/bridges/otelzap/core.go b/bridges/otelzap/core.go index f3624042790..2aebe6427cb 100644 --- a/bridges/otelzap/core.go +++ b/bridges/otelzap/core.go @@ -223,10 +223,11 @@ func (o *Core) Write(ent zapcore.Entry, fields []zapcore.Field) error { if ent.Stack != "" { r.AddAttributes(log.String(string(semconv.CodeStacktraceKey), ent.Stack)) } + emitCtx := o.ctx if len(fields) > 0 { ctx, attrbuf := convertField(fields) if ctx != nil { - o.ctx = ctx + emitCtx = ctx } r.AddAttributes(attrbuf...) } @@ -235,7 +236,7 @@ func (o *Core) Write(ent zapcore.Entry, fields []zapcore.Field) error { if ent.LoggerName != "" { logger = o.provider.Logger(ent.LoggerName, o.opts...) } - logger.Emit(o.ctx, r) + logger.Emit(emitCtx, r) return nil } diff --git a/bridges/otelzap/core_test.go b/bridges/otelzap/core_test.go index 8097780a652..3e28b89846e 100644 --- a/bridges/otelzap/core_test.go +++ b/bridges/otelzap/core_test.go @@ -5,6 +5,7 @@ package otelzap import ( "context" + "sync" "testing" "github.com/stretchr/testify/assert" @@ -144,6 +145,38 @@ func TestCore(t *testing.T) { }) } +func TestCoreConcurrentSafe(t *testing.T) { + rec := logtest.NewRecorder() + zc := NewCore(loggerName, WithLoggerProvider(rec)) + logger := zap.New(zc) + + t.Run("Write", func(t *testing.T) { + var wg sync.WaitGroup + const n = 2 + wg.Add(n) + ctx := context.Background() + for i := 0; i < n; i++ { + go func() { + defer wg.Done() + logger.Info(testMessage, zap.String(testKey, testValue), zap.Any("ctx", ctx)) + }() + } + wg.Wait() + + result := rec.Result() + require.Len(t, result, 1) + require.Len(t, result[logtest.Scope{Name: "name"}], 2) + got := result[logtest.Scope{Name: "name"}][0] + + assert.Equal(t, testMessage, got.Body.AsString()) + assert.Equal(t, log.SeverityInfo, got.Severity) + assert.Equal(t, zap.InfoLevel.String(), got.SeverityText) + assert.Equal(t, []log.KeyValue{ + log.String(testKey, testValue), + }, got.Attributes) + }) +} + func TestCoreEnabled(t *testing.T) { enabledFunc := func(c context.Context, param log.EnabledParameters) bool { return param.Severity >= log.SeverityInfo