From 5e2a0716da623ba13df562ae8f8f2fd2e5dd8f88 Mon Sep 17 00:00:00 2001 From: Rodrigo Arguello Date: Tue, 17 Sep 2024 17:34:23 +0200 Subject: [PATCH 01/12] contrib/go.uber.org/zap: add basic integration --- contrib/go.uber.org/zap/example_test.go | 29 +++++++++++++++ contrib/go.uber.org/zap/zap.go | 40 +++++++++++++++++++++ contrib/go.uber.org/zap/zap_test.go | 48 +++++++++++++++++++++++++ ddtrace/tracer/option.go | 1 + go.mod | 2 ++ go.sum | 2 ++ 6 files changed, 122 insertions(+) create mode 100644 contrib/go.uber.org/zap/example_test.go create mode 100644 contrib/go.uber.org/zap/zap.go create mode 100644 contrib/go.uber.org/zap/zap_test.go diff --git a/contrib/go.uber.org/zap/example_test.go b/contrib/go.uber.org/zap/example_test.go new file mode 100644 index 00000000000..3593fe46829 --- /dev/null +++ b/contrib/go.uber.org/zap/example_test.go @@ -0,0 +1,29 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package zap_test + +import ( + "context" + "go.uber.org/zap" + zaptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/go.uber.org/zap" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" +) + +func ExampleWithTraceFields() { + // start the DataDog tracer + tracer.Start() + defer tracer.Stop() + + // create the application logger + logger, _ := zap.NewProduction() + + // start a new span + span, ctx := tracer.StartSpanFromContext(context.Background(), "ExampleWithTraceCorrelation") + defer span.Finish() + + // log a message using the context containing span information + zaptrace.WithTraceFields(ctx, logger).Info("this is a log with tracing information") +} diff --git a/contrib/go.uber.org/zap/zap.go b/contrib/go.uber.org/zap/zap.go new file mode 100644 index 00000000000..2917d54cbc5 --- /dev/null +++ b/contrib/go.uber.org/zap/zap.go @@ -0,0 +1,40 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +// Package zap provides functions to correlate logs and traces using go.uber.org/zap package (https://github.com/uber-go/zap). +package zap // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/go.uber.org/zap" + +import ( + "context" + "go.uber.org/zap" + "strconv" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" +) + +const componentName = "go.uber.org/zap" + +func init() { + telemetry.LoadIntegration(componentName) + tracer.MarkIntegrationImported(componentName) +} + +// WithTraceFields looks for an active span in the provided context and if found, it returns a new *zap.Logger with +// traceID and spanID keys set. +func WithTraceFields(ctx context.Context, logger *zap.Logger) *zap.Logger { + span, ok := tracer.SpanFromContext(ctx) + if ok { + traceID := strconv.FormatUint(span.Context().TraceID(), 10) + spanID := strconv.FormatUint(span.Context().SpanID(), 10) + + return logger.With( + zap.String(ext.LogKeyTraceID, traceID), + zap.String(ext.LogKeySpanID, spanID), + ) + } + return logger +} diff --git a/contrib/go.uber.org/zap/zap_test.go b/contrib/go.uber.org/zap/zap_test.go new file mode 100644 index 00000000000..685b4acfae1 --- /dev/null +++ b/contrib/go.uber.org/zap/zap_test.go @@ -0,0 +1,48 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package zap + +import ( + "context" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + internallog "gopkg.in/DataDog/dd-trace-go.v1/internal/log" +) + +func TestWithTraceFields(t *testing.T) { + tracer.Start(tracer.WithLogger(internallog.DiscardLogger{})) + defer tracer.Stop() + + // start a new span + span, ctx := tracer.StartSpanFromContext(context.Background(), "test") + defer span.Finish() + + observed, logs := observer.New(zapcore.InfoLevel) + + logger := zap.New(observed) + logger = WithTraceFields(ctx, logger) + logger.Info("some message") + + traceID := strconv.FormatUint(span.Context().TraceID(), 10) + spanID := strconv.FormatUint(span.Context().SpanID(), 10) + + require.Equal(t, 1, logs.Len()) + infoLog := logs.All()[0] + + require.Len(t, infoLog.Context, 2) + assert.Equal(t, "dd.trace_id", infoLog.Context[0].Key) + assert.Equal(t, traceID, infoLog.Context[0].String) + assert.Equal(t, "dd.span_id", infoLog.Context[1].Key) + assert.Equal(t, spanID, infoLog.Context[1].String) +} diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index 9175c20efe6..e0049e16039 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -98,6 +98,7 @@ var contribIntegrations = map[string]struct { "github.com/zenazn/goji": {"Goji", false}, "log/slog": {"log/slog", false}, "github.com/uptrace/bun": {"Bun", false}, + "go.uber.org/zap": {"go.uber.org/zap", false}, } var ( diff --git a/go.mod b/go.mod index cfa9c77d8dd..d5a2bfe08d6 100644 --- a/go.mod +++ b/go.mod @@ -94,6 +94,7 @@ require ( go.opentelemetry.io/otel v1.20.0 go.opentelemetry.io/otel/trace v1.20.0 go.uber.org/atomic v1.11.0 + go.uber.org/zap v1.17.0 golang.org/x/mod v0.18.0 golang.org/x/oauth2 v0.9.0 golang.org/x/sys v0.21.0 @@ -255,6 +256,7 @@ require ( github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel/metric v1.20.0 // indirect + go.uber.org/multierr v1.6.0 // indirect golang.org/x/arch v0.4.0 // indirect golang.org/x/crypto v0.24.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect diff --git a/go.sum b/go.sum index 4e64b997d1c..984ebb61270 100644 --- a/go.sum +++ b/go.sum @@ -2182,8 +2182,10 @@ go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= From 0c53c64583f70bfde6c1aa1310eaee6c0a8ba8ec Mon Sep 17 00:00:00 2001 From: quinna-h Date: Tue, 9 Dec 2025 16:39:20 -0500 Subject: [PATCH 02/12] refactor for v2 --- contrib/go.uber.org/zap/example_test.go | 6 ++- contrib/go.uber.org/zap/zap.go | 54 +++++++++++++++++-------- contrib/go.uber.org/zap/zap_test.go | 46 +++++++++++++++++++-- instrumentation/packages.go | 5 +++ 4 files changed, 88 insertions(+), 23 deletions(-) diff --git a/contrib/go.uber.org/zap/example_test.go b/contrib/go.uber.org/zap/example_test.go index 3593fe46829..7e63a1a5bae 100644 --- a/contrib/go.uber.org/zap/example_test.go +++ b/contrib/go.uber.org/zap/example_test.go @@ -7,9 +7,11 @@ package zap_test import ( "context" + "go.uber.org/zap" - zaptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/go.uber.org/zap" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + + zaptrace "github.com/DataDog/dd-trace-go/v2/contrib/go.uber.org/zap" + "github.com/DataDog/dd-trace-go/v2/ddtrace/tracer" ) func ExampleWithTraceFields() { diff --git a/contrib/go.uber.org/zap/zap.go b/contrib/go.uber.org/zap/zap.go index 2917d54cbc5..917f6e3274b 100644 --- a/contrib/go.uber.org/zap/zap.go +++ b/contrib/go.uber.org/zap/zap.go @@ -4,37 +4,57 @@ // Copyright 2016 Datadog, Inc. // Package zap provides functions to correlate logs and traces using go.uber.org/zap package (https://github.com/uber-go/zap). -package zap // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/go.uber.org/zap" +package zap // import "github.com/DataDog/dd-trace-go/v2/contrib/go.uber.org/zap" import ( "context" - "go.uber.org/zap" "strconv" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" - "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" + "github.com/DataDog/dd-trace-go/v2/ddtrace/ext" + "github.com/DataDog/dd-trace-go/v2/ddtrace/tracer" + "github.com/DataDog/dd-trace-go/v2/instrumentation" + "github.com/DataDog/dd-trace-go/v2/instrumentation/options" + + "go.uber.org/zap" ) -const componentName = "go.uber.org/zap" +var instr *instrumentation.Instrumentation + +type config struct { + log128bits bool +} + +var cfg = newConfig() + +func newConfig() *config { + return &config{ + log128bits: options.GetBoolEnv("DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED", true), + } +} func init() { - telemetry.LoadIntegration(componentName) - tracer.MarkIntegrationImported(componentName) + instr = instrumentation.Load(instrumentation.PackageUberZap) } // WithTraceFields looks for an active span in the provided context and if found, it returns a new *zap.Logger with // traceID and spanID keys set. func WithTraceFields(ctx context.Context, logger *zap.Logger) *zap.Logger { span, ok := tracer.SpanFromContext(ctx) - if ok { - traceID := strconv.FormatUint(span.Context().TraceID(), 10) - spanID := strconv.FormatUint(span.Context().SpanID(), 10) - - return logger.With( - zap.String(ext.LogKeyTraceID, traceID), - zap.String(ext.LogKeySpanID, spanID), - ) + if !ok { + return logger + } + + var traceID string + if cfg.log128bits && span.Context().TraceID() != tracer.TraceIDZero { + traceID = span.Context().TraceID() + } else { + traceID = strconv.FormatUint(span.Context().TraceIDLower(), 10) } - return logger + + spanID := strconv.FormatUint(span.Context().SpanID(), 10) + + return logger.With( + zap.String(ext.LogKeyTraceID, traceID), + zap.String(ext.LogKeySpanID, spanID), + ) } diff --git a/contrib/go.uber.org/zap/zap_test.go b/contrib/go.uber.org/zap/zap_test.go index 685b4acfae1..4a3ac705e40 100644 --- a/contrib/go.uber.org/zap/zap_test.go +++ b/contrib/go.uber.org/zap/zap_test.go @@ -7,6 +7,7 @@ package zap import ( "context" + "os" "strconv" "testing" @@ -16,12 +17,11 @@ import ( "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" - internallog "gopkg.in/DataDog/dd-trace-go.v1/internal/log" + "github.com/DataDog/dd-trace-go/v2/ddtrace/tracer" ) func TestWithTraceFields(t *testing.T) { - tracer.Start(tracer.WithLogger(internallog.DiscardLogger{})) + tracer.Start() defer tracer.Stop() // start a new span @@ -34,7 +34,45 @@ func TestWithTraceFields(t *testing.T) { logger = WithTraceFields(ctx, logger) logger.Info("some message") - traceID := strconv.FormatUint(span.Context().TraceID(), 10) + var traceID string + spanID := strconv.FormatUint(span.Context().SpanID(), 10) + + // Re-initialize to account for race condition between setting env var in the test and reading it in the contrib + if os.Getenv("DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED") == "false" { + cfg = newConfig() + traceID = strconv.FormatUint(span.Context().TraceIDLower(), 10) + } else { + traceID = span.Context().TraceID() + } + + require.Equal(t, 1, logs.Len()) + infoLog := logs.All()[0] + + require.Len(t, infoLog.Context, 2) + assert.Equal(t, "dd.trace_id", infoLog.Context[0].Key) + assert.Equal(t, traceID, infoLog.Context[0].String) + assert.Equal(t, "dd.span_id", infoLog.Context[1].Key) + assert.Equal(t, spanID, infoLog.Context[1].String) +} + +func TestWithTraceFields128BitDisabled(t *testing.T) { + t.Setenv("DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED", "false") + + cfg = newConfig() + + tracer.Start() + defer tracer.Stop() + + span, ctx := tracer.StartSpanFromContext(context.Background(), "test") + defer span.Finish() + + observed, logs := observer.New(zapcore.InfoLevel) + + logger := zap.New(observed) + logger = WithTraceFields(ctx, logger) + logger.Info("some message") + + traceID := strconv.FormatUint(span.Context().TraceIDLower(), 10) spanID := strconv.FormatUint(span.Context().SpanID(), 10) require.Equal(t, 1, logs.Len()) diff --git a/instrumentation/packages.go b/instrumentation/packages.go index 1626978b935..220011a2927 100644 --- a/instrumentation/packages.go +++ b/instrumentation/packages.go @@ -74,6 +74,7 @@ const ( PackageUptraceBun Package = "uptrace/bun" PackageLogSlog Package = "log/slog" PackageModelContextProtocolGoSDK Package = "modelcontextprotocol/go-sdk" + PackageUberZap Package = "go.uber.org/zap" PackageValkeyIoValkeyGo Package = "valkey-io/valkey-go" PackageEnvoyProxyGoControlPlane Package = "envoyproxy/go-control-plane" @@ -815,6 +816,10 @@ var packages = map[Package]PackageInfo{ TracedPackage: "log/slog", IsStdLib: true, }, + PackageUberZap: { + TracedPackage: "go.uber.org/zap", + EnvVarPrefix: "ZAP", + }, PackageValkeyIoValkeyGo: { TracedPackage: "github.com/valkey-io/valkey-go", EnvVarPrefix: "VALKEY", From 9ac5e3665fb6c14097cb1f64b5cb39f5ef1f4231 Mon Sep 17 00:00:00 2001 From: quinna-h Date: Tue, 9 Dec 2025 16:48:49 -0500 Subject: [PATCH 03/12] wip --- contrib/go.uber.org/zap/zap_test.go | 30 ----------------------------- 1 file changed, 30 deletions(-) diff --git a/contrib/go.uber.org/zap/zap_test.go b/contrib/go.uber.org/zap/zap_test.go index 4a3ac705e40..d4eedea6d5d 100644 --- a/contrib/go.uber.org/zap/zap_test.go +++ b/contrib/go.uber.org/zap/zap_test.go @@ -54,33 +54,3 @@ func TestWithTraceFields(t *testing.T) { assert.Equal(t, "dd.span_id", infoLog.Context[1].Key) assert.Equal(t, spanID, infoLog.Context[1].String) } - -func TestWithTraceFields128BitDisabled(t *testing.T) { - t.Setenv("DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED", "false") - - cfg = newConfig() - - tracer.Start() - defer tracer.Stop() - - span, ctx := tracer.StartSpanFromContext(context.Background(), "test") - defer span.Finish() - - observed, logs := observer.New(zapcore.InfoLevel) - - logger := zap.New(observed) - logger = WithTraceFields(ctx, logger) - logger.Info("some message") - - traceID := strconv.FormatUint(span.Context().TraceIDLower(), 10) - spanID := strconv.FormatUint(span.Context().SpanID(), 10) - - require.Equal(t, 1, logs.Len()) - infoLog := logs.All()[0] - - require.Len(t, infoLog.Context, 2) - assert.Equal(t, "dd.trace_id", infoLog.Context[0].Key) - assert.Equal(t, traceID, infoLog.Context[0].String) - assert.Equal(t, "dd.span_id", infoLog.Context[1].Key) - assert.Equal(t, spanID, infoLog.Context[1].String) -} From cc4f8ebde293a816fa4f32dbfb95525dbb11fa4f Mon Sep 17 00:00:00 2001 From: quinna-h Date: Wed, 10 Dec 2025 10:55:25 -0500 Subject: [PATCH 04/12] small update --- contrib/go.uber.org/zap/zap_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contrib/go.uber.org/zap/zap_test.go b/contrib/go.uber.org/zap/zap_test.go index d4eedea6d5d..8b51e12ab2e 100644 --- a/contrib/go.uber.org/zap/zap_test.go +++ b/contrib/go.uber.org/zap/zap_test.go @@ -18,10 +18,13 @@ import ( "go.uber.org/zap/zaptest/observer" "github.com/DataDog/dd-trace-go/v2/ddtrace/tracer" + "github.com/DataDog/dd-trace-go/v2/instrumentation/testutils" ) func TestWithTraceFields(t *testing.T) { - tracer.Start() + tracer.Start( + tracer.WithLogger(testutils.DiscardLogger()), + ) defer tracer.Stop() // start a new span From 8ae201e888a1f8e574d9b96bfb229458b75adb03 Mon Sep 17 00:00:00 2001 From: quinna-h Date: Wed, 10 Dec 2025 11:29:46 -0500 Subject: [PATCH 05/12] add test --- contrib/go.uber.org/zap/zap_test.go | 43 +++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/contrib/go.uber.org/zap/zap_test.go b/contrib/go.uber.org/zap/zap_test.go index 8b51e12ab2e..41a454cab6f 100644 --- a/contrib/go.uber.org/zap/zap_test.go +++ b/contrib/go.uber.org/zap/zap_test.go @@ -7,7 +7,6 @@ package zap import ( "context" - "os" "strconv" "testing" @@ -37,16 +36,44 @@ func TestWithTraceFields(t *testing.T) { logger = WithTraceFields(ctx, logger) logger.Info("some message") - var traceID string + // By default, 128-bit trace IDs are enabled + traceID := span.Context().TraceID() spanID := strconv.FormatUint(span.Context().SpanID(), 10) + require.Equal(t, 1, logs.Len()) + infoLog := logs.All()[0] + + require.Len(t, infoLog.Context, 2) + assert.Equal(t, "dd.trace_id", infoLog.Context[0].Key) + assert.Equal(t, traceID, infoLog.Context[0].String) + assert.Equal(t, "dd.span_id", infoLog.Context[1].Key) + assert.Equal(t, spanID, infoLog.Context[1].String) +} + +func TestWithTraceFields128BitDisabled(t *testing.T) { + t.Setenv("DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED", "false") + // Re-initialize to account for race condition between setting env var in the test and reading it in the contrib - if os.Getenv("DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED") == "false" { - cfg = newConfig() - traceID = strconv.FormatUint(span.Context().TraceIDLower(), 10) - } else { - traceID = span.Context().TraceID() - } + cfg = newConfig() + + tracer.Start( + tracer.WithLogger(testutils.DiscardLogger()), + ) + defer tracer.Stop() + + // start a new span + span, ctx := tracer.StartSpanFromContext(context.Background(), "test") + defer span.Finish() + + observed, logs := observer.New(zapcore.InfoLevel) + + logger := zap.New(observed) + logger = WithTraceFields(ctx, logger) + logger.Info("some message") + + // With 128-bit disabled, should use 64-bit trace ID + traceID := strconv.FormatUint(span.Context().TraceIDLower(), 10) + spanID := strconv.FormatUint(span.Context().SpanID(), 10) require.Equal(t, 1, logs.Len()) infoLog := logs.All()[0] From 51bdb300d926d7b40f1042f791171ff3f8417c09 Mon Sep 17 00:00:00 2001 From: quinna-h Date: Wed, 10 Dec 2025 12:40:57 -0500 Subject: [PATCH 06/12] clean go mod and go sum --- go.mod | 3 +-- go.sum | 7 ------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 11117bbc644..8fdb69fe79a 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,6 @@ require ( github.com/quasilyte/go-ruleguard/dsl v0.3.22 github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 github.com/spaolacci/murmur3 v1.1.0 - go.uber.org/zap v1.17.0 github.com/stretchr/testify v1.11.1 github.com/theckman/httpforwarded v0.4.0 github.com/tinylib/msgp v1.3.0 @@ -40,6 +39,7 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.38.0 go.opentelemetry.io/otel/trace v1.38.0 go.uber.org/goleak v1.3.0 + go.uber.org/zap v1.27.0 go.yaml.in/yaml/v3 v3.0.4 golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 golang.org/x/mod v0.29.0 @@ -99,7 +99,6 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/mock v0.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect golang.org/x/net v0.46.0 // indirect golang.org/x/text v0.30.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect diff --git a/go.sum b/go.sum index 97624a1d23d..233593fcb49 100644 --- a/go.sum +++ b/go.sum @@ -235,13 +235,6 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= -golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= From 5de13ef5f1338cd368baf7707bb793c974b30480 Mon Sep 17 00:00:00 2001 From: quinna-h Date: Wed, 10 Dec 2025 13:32:09 -0500 Subject: [PATCH 07/12] address smoke test failure --- internal/env/supported_configurations.gen.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/env/supported_configurations.gen.go b/internal/env/supported_configurations.gen.go index 5ee5c85052f..a5956f7cd42 100644 --- a/internal/env/supported_configurations.gen.go +++ b/internal/env/supported_configurations.gen.go @@ -227,8 +227,8 @@ var SupportedConfigurations = map[string]struct{}{ "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": {}, "OTEL_EXPORTER_OTLP_METRICS_HEADERS": {}, "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL": {}, - "OTEL_EXPORTER_OTLP_METRICS_TIMEOUT": {}, "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE": {}, + "OTEL_EXPORTER_OTLP_METRICS_TIMEOUT": {}, "OTEL_EXPORTER_OTLP_PROTOCOL": {}, "OTEL_LOGS_EXPORTER": {}, "OTEL_LOG_LEVEL": {}, From b1d3837b80fbe87d23c5f47bd8200dc81678aa6d Mon Sep 17 00:00:00 2001 From: quinna-h Date: Wed, 10 Dec 2025 13:42:59 -0500 Subject: [PATCH 08/12] update to make tests happy --- ddtrace/tracer/option_test.go | 2 +- instrumentation/instrumentation_test.go | 5 +++++ internal/env/supported_configurations.json | 25 ++++++++++++---------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/ddtrace/tracer/option_test.go b/ddtrace/tracer/option_test.go index b08360eca47..e50491a3ab9 100644 --- a/ddtrace/tracer/option_test.go +++ b/ddtrace/tracer/option_test.go @@ -293,7 +293,7 @@ func TestAgentIntegration(t *testing.T) { defer clearIntegrationsForTests() cfg.loadContribIntegrations(nil) - assert.Equal(t, 55, len(cfg.integrations)) + assert.Equal(t, 56, len(cfg.integrations)) for integrationName, v := range cfg.integrations { assert.False(t, v.Instrumented, "integrationName=%s", integrationName) } diff --git a/instrumentation/instrumentation_test.go b/instrumentation/instrumentation_test.go index b1812b6d9b6..97dc32702b0 100644 --- a/instrumentation/instrumentation_test.go +++ b/instrumentation/instrumentation_test.go @@ -21,6 +21,11 @@ func TestInstrumentation_AnalyticsRate(t *testing.T) { t.Skip("Lambda contrib does not implement analytics functionality") return } + // Skip logging integrations - they don't generate spans, just correlate logs with traces + if pkg == PackageSirupsenLogrus || pkg == PackageLogSlog || pkg == PackageUberZap { + t.Skip("Logging integrations do not implement analytics functionality") + return + } instr := Load(pkg) diff --git a/internal/env/supported_configurations.json b/internal/env/supported_configurations.json index 46b0dad5e06..5689112e669 100644 --- a/internal/env/supported_configurations.json +++ b/internal/env/supported_configurations.json @@ -102,10 +102,10 @@ "DD_CIVISIBILITY_SUBTEST_FEATURES_ENABLED": [ "A" ], - "DD_CIVISIBILITY_USE_NOOP_TRACER": [ + "DD_CIVISIBILITY_TOTAL_FLAKY_RETRY_COUNT": [ "A" ], - "DD_CIVISIBILITY_TOTAL_FLAKY_RETRY_COUNT": [ + "DD_CIVISIBILITY_USE_NOOP_TRACER": [ "A" ], "DD_CUSTOM_TRACE_ID": [ @@ -219,6 +219,9 @@ "DD_LOGGING_RATE": [ "A" ], + "DD_METRICS_OTEL_ENABLED": [ + "A" + ], "DD_PIPELINE_EXECUTION_ID": [ "A" ], @@ -633,34 +636,34 @@ "DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH": [ "A" ], - "DD_TRACE__ANALYTICS_ENABLED": [ + "DD_TRACE_ZAP_ANALYTICS_ENABLED": [ "A" ], - "DD_VERSION": [ + "DD_TRACE__ANALYTICS_ENABLED": [ "A" ], - "DD_METRICS_OTEL_ENABLED": [ + "DD_VERSION": [ "A" ], "OTEL_EXPORTER_OTLP_ENDPOINT": [ "A" ], - "OTEL_EXPORTER_OTLP_METRICS_HEADERS": [ + "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": [ "A" ], - "OTEL_EXPORTER_OTLP_PROTOCOL": [ + "OTEL_EXPORTER_OTLP_METRICS_HEADERS": [ "A" ], - "OTEL_EXPORTER_OTLP_METRICS_TIMEOUT": [ + "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL": [ "A" ], - "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": [ + "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE": [ "A" ], - "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL": [ + "OTEL_EXPORTER_OTLP_METRICS_TIMEOUT": [ "A" ], - "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE": [ + "OTEL_EXPORTER_OTLP_PROTOCOL": [ "A" ], "OTEL_LOGS_EXPORTER": [ From 8d8be5249232c8ee39b28b31a5d2a71e35699ad7 Mon Sep 17 00:00:00 2001 From: quinna-h Date: Wed, 10 Dec 2025 13:57:10 -0500 Subject: [PATCH 09/12] wip --- internal/env/supported_configurations.gen.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/env/supported_configurations.gen.go b/internal/env/supported_configurations.gen.go index a5956f7cd42..835dbca2bc1 100644 --- a/internal/env/supported_configurations.gen.go +++ b/internal/env/supported_configurations.gen.go @@ -221,6 +221,7 @@ var SupportedConfigurations = map[string]struct{}{ "DD_TRACE_VALKEY_RAW_COMMAND": {}, "DD_TRACE_VAULT_ANALYTICS_ENABLED": {}, "DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH": {}, + "DD_TRACE_ZAP_ANALYTICS_ENABLED": {}, "DD_TRACE__ANALYTICS_ENABLED": {}, "DD_VERSION": {}, "OTEL_EXPORTER_OTLP_ENDPOINT": {}, From 12bbd78472f31d963130640ba834213adb0396bf Mon Sep 17 00:00:00 2001 From: quinna-h Date: Wed, 14 Jan 2026 11:12:20 -0500 Subject: [PATCH 10/12] add orchestrion - manual trace injection --- contrib/go.uber.org/zap/core.go | 59 ++++++++ contrib/go.uber.org/zap/orchestrion.yml | 35 +++++ .../_integration/zap/generated_test.go | 18 +++ internal/orchestrion/_integration/zap/zap.go | 127 ++++++++++++++++++ internal/orchestrion/context.go | 11 ++ 5 files changed, 250 insertions(+) create mode 100644 contrib/go.uber.org/zap/core.go create mode 100644 contrib/go.uber.org/zap/orchestrion.yml create mode 100644 internal/orchestrion/_integration/zap/generated_test.go create mode 100644 internal/orchestrion/_integration/zap/zap.go diff --git a/contrib/go.uber.org/zap/core.go b/contrib/go.uber.org/zap/core.go new file mode 100644 index 00000000000..8dbb8074dff --- /dev/null +++ b/contrib/go.uber.org/zap/core.go @@ -0,0 +1,59 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package zap + +import ( + "go.uber.org/zap/zapcore" +) + +var _ zapcore.Core = (*traceCore)(nil) + +// traceCore is a zapcore.Core wrapper that can be used for trace context injection. +// Note: Automatic trace context injection via GLS is not currently supported for zap +// because zap's Write method does not receive a context.Context. +// Use WithTraceFields(ctx, logger) for manual trace context injection. +type traceCore struct { + zapcore.Core +} + +// WrapCore wraps a zapcore.Core. This is a hook for potential future enhancements. +// Currently, it simply returns a wrapped core that passes through to the underlying core. +func WrapCore(core zapcore.Core) zapcore.Core { + if _, ok := core.(*traceCore); ok { + return core // Already wrapped + } + return &traceCore{Core: core} +} + +// IsAlreadyWrapped checks whether the given core is already wrapped by this package. +func IsAlreadyWrapped(core zapcore.Core) bool { + _, ok := core.(*traceCore) + return ok +} + +// With creates a new core with additional fields. +func (c *traceCore) With(fields []zapcore.Field) zapcore.Core { + return &traceCore{Core: c.Core.With(fields)} +} + +// Check determines whether the supplied Entry should be logged. +func (c *traceCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { + if c.Core.Enabled(ent.Level) { + return ce.AddCore(ent, c) + } + return ce +} + +// Write serializes the Entry and any Fields supplied at the log site and +// writes them to their destination. +func (c *traceCore) Write(ent zapcore.Entry, fields []zapcore.Field) error { + return c.Core.Write(ent, fields) +} + +// Sync flushes buffered logs (if any). +func (c *traceCore) Sync() error { + return c.Core.Sync() +} diff --git a/contrib/go.uber.org/zap/orchestrion.yml b/contrib/go.uber.org/zap/orchestrion.yml new file mode 100644 index 00000000000..4bcbd03a2f8 --- /dev/null +++ b/contrib/go.uber.org/zap/orchestrion.yml @@ -0,0 +1,35 @@ +# Unless explicitly stated otherwise all files in this repository are licensed +# under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2023-present Datadog, Inc. +--- +# yaml-language-server: $schema=https://datadoghq.dev/orchestrion/schema.json +meta: + name: github.com/DataDog/dd-trace-go/v2/contrib/go.uber.org/zap + description: |- + Blazing fast, structured, leveled logging in Go. This integration wraps zap + constructors for potential future enhancements. Note: Automatic trace context + injection is not currently supported because zap's Write method does not receive + a context. Use WithTraceFields(ctx, logger) for manual trace injection. + +aspects: + # Wrap zap.New to inject trace-aware core + - id: New + join-point: + all-of: + - import-path: go.uber.org/zap + - function-body: + function: + - name: New + advice: + - inject-declarations: + imports: + zapcore: go.uber.org/zap/zapcore + links: + - github.com/DataDog/dd-trace-go/v2/contrib/go.uber.org/zap + template: |- + //go:linkname __dd_zaptrace_WrapCore github.com/DataDog/dd-trace-go/v2/contrib/go.uber.org/zap.WrapCore + func __dd_zaptrace_WrapCore(zapcore.Core) zapcore.Core + - prepend-statements: + template: |- + {{ .Function.Argument 0 }} = __dd_zaptrace_WrapCore({{ .Function.Argument 0 }}) diff --git a/internal/orchestrion/_integration/zap/generated_test.go b/internal/orchestrion/_integration/zap/generated_test.go new file mode 100644 index 00000000000..99d2331dd77 --- /dev/null +++ b/internal/orchestrion/_integration/zap/generated_test.go @@ -0,0 +1,18 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2023-present Datadog, Inc. +// +// Code generated by 'go generate'; DO NOT EDIT. + +package zap + +import ( + "testing" + + "github.com/DataDog/dd-trace-go/v2/internal/orchestrion/_integration/internal/harness" +) + +func Test(t *testing.T) { + harness.Run(t, new(TestCase)) +} diff --git a/internal/orchestrion/_integration/zap/zap.go b/internal/orchestrion/_integration/zap/zap.go new file mode 100644 index 00000000000..b9c995d6ed4 --- /dev/null +++ b/internal/orchestrion/_integration/zap/zap.go @@ -0,0 +1,127 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2023-present Datadog, Inc. + +package zap + +import ( + "bytes" + "context" + "strings" + "testing" + + "github.com/DataDog/dd-trace-go/v2/internal/orchestrion" + "github.com/DataDog/dd-trace-go/v2/internal/orchestrion/_integration/internal/trace" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// orchestrionEnabled returns whether orchestrion is enabled +func orchestrionEnabled() bool { + return orchestrion.Enabled() +} + +type TestCase struct { + logger *zap.Logger + logs *bytes.Buffer +} + +func (tc *TestCase) Setup(_ context.Context, t *testing.T) { + tc.logs = new(bytes.Buffer) + + // Debug: Check if orchestrion is enabled + t.Logf("orchestrion.Enabled() = %v", orchestrionEnabled()) + + // Create a custom encoder config for readable test output + encoderCfg := zapcore.EncoderConfig{ + MessageKey: "msg", + LevelKey: "level", + TimeKey: "time", + NameKey: "logger", + CallerKey: "caller", + StacktraceKey: "stacktrace", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + } + + core := zapcore.NewCore( + zapcore.NewJSONEncoder(encoderCfg), + zapcore.AddSync(tc.logs), + zapcore.DebugLevel, + ) + + // zap.New is instrumented by orchestrion to wrap the core + tc.logger = zap.New(core) + + // Debug: Log the core type to verify wrapping happened + t.Logf("Created logger with core type: %T", core) +} + +//dd:span +func Log(ctx context.Context, logger *zap.Logger, level zapcore.Level, msg string) { + _ = ctx // ctx is used by orchestrion to inject trace context via GLS + switch level { + case zapcore.DebugLevel: + logger.Debug(msg) + case zapcore.InfoLevel: + logger.Info(msg) + case zapcore.WarnLevel: + logger.Warn(msg) + case zapcore.ErrorLevel: + logger.Error(msg) + } +} + +func (tc *TestCase) Run(ctx context.Context, t *testing.T) { + Log(ctx, tc.logger, zapcore.DebugLevel, "debug") + Log(ctx, tc.logger, zapcore.InfoLevel, "info") + Log(ctx, tc.logger, zapcore.WarnLevel, "warn") + Log(ctx, tc.logger, zapcore.ErrorLevel, "error") + + // Sync to flush any buffered log entries + _ = tc.logger.Sync() + + logs := tc.logs.String() + t.Logf("got logs: %s", logs) + + // Verify all log messages are present + for _, msg := range []string{"debug", "info", "warn", "error"} { + want := `"msg":"` + msg + `"` + if !strings.Contains(logs, want) { + t.Fatalf("missing log message %s", msg) + } + } + + // Note: Automatic trace context injection is not currently supported for zap + // because zap's Write method does not receive a context.Context. + // Use WithTraceFields(ctx, logger) for manual trace injection. +} + +func (*TestCase) ExpectedTraces() trace.Traces { + return trace.Traces{ + { + Tags: map[string]any{ + "name": "Log", + }, + }, + { + Tags: map[string]any{ + "name": "Log", + }, + }, + { + Tags: map[string]any{ + "name": "Log", + }, + }, + { + Tags: map[string]any{ + "name": "Log", + }, + }, + } +} diff --git a/internal/orchestrion/context.go b/internal/orchestrion/context.go index f66d12c5761..baf2ddf9771 100644 --- a/internal/orchestrion/context.go +++ b/internal/orchestrion/context.go @@ -41,6 +41,17 @@ func CtxWithValue(parent context.Context, key, val any) context.Context { return context.WithValue(WrapContext(parent), key, val) } +// GLSPeekValue returns the value from the GLS slot of orchestrion without removing it. +// This is useful for reading the current active span without modifying the stack, +// such as when injecting trace context into log entries. +func GLSPeekValue(key any) any { + if !Enabled() { + return nil + } + + return getDDContextStack().Peek(key) +} + // GLSPopValue pops the value from the GLS slot of orchestrion and returns it. Using context.Context values usually does // not require to pop any stack because the copy of each previous context makes the local variable in the scope disappear // when the current function ends. But the GLS is a semi-global variable that can be accessed from any function in the From 6402c1ad4a0044e5efe211912c980d79f51d4644 Mon Sep 17 00:00:00 2001 From: quinna-h Date: Wed, 14 Jan 2026 14:50:44 -0500 Subject: [PATCH 11/12] automatic trace injection with GLS --- contrib/go.uber.org/zap/core.go | 66 ++++++++++++++++---- internal/orchestrion/_integration/zap/zap.go | 24 ++++--- orchestrion/all/orchestrion.tool.go | 1 + 3 files changed, 70 insertions(+), 21 deletions(-) diff --git a/contrib/go.uber.org/zap/core.go b/contrib/go.uber.org/zap/core.go index 8dbb8074dff..9118d09a17f 100644 --- a/contrib/go.uber.org/zap/core.go +++ b/contrib/go.uber.org/zap/core.go @@ -6,21 +6,29 @@ package zap import ( + "strconv" + + "github.com/DataDog/dd-trace-go/v2/ddtrace/ext" + "github.com/DataDog/dd-trace-go/v2/ddtrace/tracer" + "github.com/DataDog/dd-trace-go/v2/internal" + "github.com/DataDog/dd-trace-go/v2/internal/orchestrion" + + "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var _ zapcore.Core = (*traceCore)(nil) -// traceCore is a zapcore.Core wrapper that can be used for trace context injection. -// Note: Automatic trace context injection via GLS is not currently supported for zap -// because zap's Write method does not receive a context.Context. -// Use WithTraceFields(ctx, logger) for manual trace context injection. +// traceCore is a zapcore.Core wrapper that automatically injects trace context +// from GLS (Go Local Storage) when built with Orchestrion. type traceCore struct { zapcore.Core } -// WrapCore wraps a zapcore.Core. This is a hook for potential future enhancements. -// Currently, it simply returns a wrapped core that passes through to the underlying core. +// Wraps a zapcore.Core with automatic trace context injection. +// When the application is built with Orchestrion, this core will automatically +// add dd.trace_id and dd.span_id fields to log entries if an active span exists +// in the current goroutine's context. func WrapCore(core zapcore.Core) zapcore.Core { if _, ok := core.(*traceCore); ok { return core // Already wrapped @@ -28,18 +36,18 @@ func WrapCore(core zapcore.Core) zapcore.Core { return &traceCore{Core: core} } -// IsAlreadyWrapped checks whether the given core is already wrapped by this package. +// Checks whether the given core is already wrapped by this package. func IsAlreadyWrapped(core zapcore.Core) bool { _, ok := core.(*traceCore) return ok } -// With creates a new core with additional fields. +// Creates a new core with additional fields. func (c *traceCore) With(fields []zapcore.Field) zapcore.Core { return &traceCore{Core: c.Core.With(fields)} } -// Check determines whether the supplied Entry should be logged. +// Returns whether the supplied Entry should be logged. func (c *traceCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { if c.Core.Enabled(ent.Level) { return ce.AddCore(ent, c) @@ -47,13 +55,47 @@ func (c *traceCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore. return ce } -// Write serializes the Entry and any Fields supplied at the log site and -// writes them to their destination. +// Serializes the Entry and any Fields supplied at the log site and +// writes them to their destination, injecting trace context if available. func (c *traceCore) Write(ent zapcore.Entry, fields []zapcore.Field) error { + fields = c.withTraceFieldsFromGLS(fields) return c.Core.Write(ent, fields) } -// Sync flushes buffered logs (if any). +// Flushes buffered logs (if any) func (c *traceCore) Sync() error { return c.Core.Sync() } + +// Adds trace context fields from GLS if orchestrion is enabled +// and an active span exists in the current goroutine. +func (c *traceCore) withTraceFieldsFromGLS(fields []zapcore.Field) []zapcore.Field { + if !orchestrion.Enabled() { + return fields + } + + spanVal := orchestrion.GLSPeekValue(internal.ActiveSpanKey) + if spanVal == nil { + return fields + } + + span, ok := spanVal.(*tracer.Span) + if !ok || span == nil { + return fields + } + + spanCtx := span.Context() + var traceID string + if cfg.log128bits && spanCtx.TraceID() != tracer.TraceIDZero { + traceID = spanCtx.TraceID() + } else { + traceID = strconv.FormatUint(spanCtx.TraceIDLower(), 10) + } + + spanID := strconv.FormatUint(spanCtx.SpanID(), 10) + + return append(fields, + zap.String(ext.LogKeyTraceID, traceID), + zap.String(ext.LogKeySpanID, spanID), + ) +} diff --git a/internal/orchestrion/_integration/zap/zap.go b/internal/orchestrion/_integration/zap/zap.go index b9c995d6ed4..8b79d6fa663 100644 --- a/internal/orchestrion/_integration/zap/zap.go +++ b/internal/orchestrion/_integration/zap/zap.go @@ -8,6 +8,7 @@ package zap import ( "bytes" "context" + "regexp" "strings" "testing" @@ -17,7 +18,6 @@ import ( "go.uber.org/zap/zapcore" ) -// orchestrionEnabled returns whether orchestrion is enabled func orchestrionEnabled() bool { return orchestrion.Enabled() } @@ -30,10 +30,8 @@ type TestCase struct { func (tc *TestCase) Setup(_ context.Context, t *testing.T) { tc.logs = new(bytes.Buffer) - // Debug: Check if orchestrion is enabled t.Logf("orchestrion.Enabled() = %v", orchestrionEnabled()) - // Create a custom encoder config for readable test output encoderCfg := zapcore.EncoderConfig{ MessageKey: "msg", LevelKey: "level", @@ -54,7 +52,6 @@ func (tc *TestCase) Setup(_ context.Context, t *testing.T) { zapcore.DebugLevel, ) - // zap.New is instrumented by orchestrion to wrap the core tc.logger = zap.New(core) // Debug: Log the core type to verify wrapping happened @@ -82,13 +79,11 @@ func (tc *TestCase) Run(ctx context.Context, t *testing.T) { Log(ctx, tc.logger, zapcore.WarnLevel, "warn") Log(ctx, tc.logger, zapcore.ErrorLevel, "error") - // Sync to flush any buffered log entries _ = tc.logger.Sync() logs := tc.logs.String() t.Logf("got logs: %s", logs) - // Verify all log messages are present for _, msg := range []string{"debug", "info", "warn", "error"} { want := `"msg":"` + msg + `"` if !strings.Contains(logs, want) { @@ -96,9 +91,20 @@ func (tc *TestCase) Run(ctx context.Context, t *testing.T) { } } - // Note: Automatic trace context injection is not currently supported for zap - // because zap's Write method does not receive a context.Context. - // Use WithTraceFields(ctx, logger) for manual trace injection. + // Verify trace context is injected into each log line via GLS + lines := strings.Split(strings.TrimSpace(logs), "\n") + for _, line := range lines { + if line == "" { + continue + } + t.Logf("checking line: %s", line) + if ok, _ := regexp.MatchString(`"dd\.span_id":"\d+"`, line); !ok { + t.Errorf("no span ID in log line: %s", line) + } + if ok, _ := regexp.MatchString(`"dd\.trace_id":"[0-9a-f]+"`, line); !ok { + t.Errorf("no trace ID in log line: %s", line) + } + } } func (*TestCase) ExpectedTraces() trace.Traces { diff --git a/orchestrion/all/orchestrion.tool.go b/orchestrion/all/orchestrion.tool.go index f0196d8f3ea..a03753ee47b 100644 --- a/orchestrion/all/orchestrion.tool.go +++ b/orchestrion/all/orchestrion.tool.go @@ -63,6 +63,7 @@ import ( _ "github.com/DataDog/dd-trace-go/contrib/twitchtv/twirp/v2" // integration _ "github.com/DataDog/dd-trace-go/contrib/valkey-io/valkey-go/v2" // integration _ "github.com/DataDog/dd-trace-go/contrib/valyala/fasthttp/v2" // integration + _ "github.com/DataDog/dd-trace-go/v2/contrib/go.uber.org/zap" // integration _ "github.com/DataDog/dd-trace-go/v2/contrib/os" // integration _ "github.com/DataDog/dd-trace-go/v2/ddtrace/tracer" // integration _ "github.com/DataDog/dd-trace-go/v2/orchestrion" // integration From 282492fc46bab28c1107a2d4fc93c9de860bdfa9 Mon Sep 17 00:00:00 2001 From: quinna-h Date: Thu, 22 Jan 2026 09:38:02 -0500 Subject: [PATCH 12/12] Revert "automatic trace injection with GLS" This reverts commit 6402c1ad4a0044e5efe211912c980d79f51d4644. --- contrib/go.uber.org/zap/core.go | 66 ++++---------------- internal/orchestrion/_integration/zap/zap.go | 24 +++---- orchestrion/all/orchestrion.tool.go | 1 - 3 files changed, 21 insertions(+), 70 deletions(-) diff --git a/contrib/go.uber.org/zap/core.go b/contrib/go.uber.org/zap/core.go index 9118d09a17f..8dbb8074dff 100644 --- a/contrib/go.uber.org/zap/core.go +++ b/contrib/go.uber.org/zap/core.go @@ -6,29 +6,21 @@ package zap import ( - "strconv" - - "github.com/DataDog/dd-trace-go/v2/ddtrace/ext" - "github.com/DataDog/dd-trace-go/v2/ddtrace/tracer" - "github.com/DataDog/dd-trace-go/v2/internal" - "github.com/DataDog/dd-trace-go/v2/internal/orchestrion" - - "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var _ zapcore.Core = (*traceCore)(nil) -// traceCore is a zapcore.Core wrapper that automatically injects trace context -// from GLS (Go Local Storage) when built with Orchestrion. +// traceCore is a zapcore.Core wrapper that can be used for trace context injection. +// Note: Automatic trace context injection via GLS is not currently supported for zap +// because zap's Write method does not receive a context.Context. +// Use WithTraceFields(ctx, logger) for manual trace context injection. type traceCore struct { zapcore.Core } -// Wraps a zapcore.Core with automatic trace context injection. -// When the application is built with Orchestrion, this core will automatically -// add dd.trace_id and dd.span_id fields to log entries if an active span exists -// in the current goroutine's context. +// WrapCore wraps a zapcore.Core. This is a hook for potential future enhancements. +// Currently, it simply returns a wrapped core that passes through to the underlying core. func WrapCore(core zapcore.Core) zapcore.Core { if _, ok := core.(*traceCore); ok { return core // Already wrapped @@ -36,18 +28,18 @@ func WrapCore(core zapcore.Core) zapcore.Core { return &traceCore{Core: core} } -// Checks whether the given core is already wrapped by this package. +// IsAlreadyWrapped checks whether the given core is already wrapped by this package. func IsAlreadyWrapped(core zapcore.Core) bool { _, ok := core.(*traceCore) return ok } -// Creates a new core with additional fields. +// With creates a new core with additional fields. func (c *traceCore) With(fields []zapcore.Field) zapcore.Core { return &traceCore{Core: c.Core.With(fields)} } -// Returns whether the supplied Entry should be logged. +// Check determines whether the supplied Entry should be logged. func (c *traceCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { if c.Core.Enabled(ent.Level) { return ce.AddCore(ent, c) @@ -55,47 +47,13 @@ func (c *traceCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore. return ce } -// Serializes the Entry and any Fields supplied at the log site and -// writes them to their destination, injecting trace context if available. +// Write serializes the Entry and any Fields supplied at the log site and +// writes them to their destination. func (c *traceCore) Write(ent zapcore.Entry, fields []zapcore.Field) error { - fields = c.withTraceFieldsFromGLS(fields) return c.Core.Write(ent, fields) } -// Flushes buffered logs (if any) +// Sync flushes buffered logs (if any). func (c *traceCore) Sync() error { return c.Core.Sync() } - -// Adds trace context fields from GLS if orchestrion is enabled -// and an active span exists in the current goroutine. -func (c *traceCore) withTraceFieldsFromGLS(fields []zapcore.Field) []zapcore.Field { - if !orchestrion.Enabled() { - return fields - } - - spanVal := orchestrion.GLSPeekValue(internal.ActiveSpanKey) - if spanVal == nil { - return fields - } - - span, ok := spanVal.(*tracer.Span) - if !ok || span == nil { - return fields - } - - spanCtx := span.Context() - var traceID string - if cfg.log128bits && spanCtx.TraceID() != tracer.TraceIDZero { - traceID = spanCtx.TraceID() - } else { - traceID = strconv.FormatUint(spanCtx.TraceIDLower(), 10) - } - - spanID := strconv.FormatUint(spanCtx.SpanID(), 10) - - return append(fields, - zap.String(ext.LogKeyTraceID, traceID), - zap.String(ext.LogKeySpanID, spanID), - ) -} diff --git a/internal/orchestrion/_integration/zap/zap.go b/internal/orchestrion/_integration/zap/zap.go index 8b79d6fa663..b9c995d6ed4 100644 --- a/internal/orchestrion/_integration/zap/zap.go +++ b/internal/orchestrion/_integration/zap/zap.go @@ -8,7 +8,6 @@ package zap import ( "bytes" "context" - "regexp" "strings" "testing" @@ -18,6 +17,7 @@ import ( "go.uber.org/zap/zapcore" ) +// orchestrionEnabled returns whether orchestrion is enabled func orchestrionEnabled() bool { return orchestrion.Enabled() } @@ -30,8 +30,10 @@ type TestCase struct { func (tc *TestCase) Setup(_ context.Context, t *testing.T) { tc.logs = new(bytes.Buffer) + // Debug: Check if orchestrion is enabled t.Logf("orchestrion.Enabled() = %v", orchestrionEnabled()) + // Create a custom encoder config for readable test output encoderCfg := zapcore.EncoderConfig{ MessageKey: "msg", LevelKey: "level", @@ -52,6 +54,7 @@ func (tc *TestCase) Setup(_ context.Context, t *testing.T) { zapcore.DebugLevel, ) + // zap.New is instrumented by orchestrion to wrap the core tc.logger = zap.New(core) // Debug: Log the core type to verify wrapping happened @@ -79,11 +82,13 @@ func (tc *TestCase) Run(ctx context.Context, t *testing.T) { Log(ctx, tc.logger, zapcore.WarnLevel, "warn") Log(ctx, tc.logger, zapcore.ErrorLevel, "error") + // Sync to flush any buffered log entries _ = tc.logger.Sync() logs := tc.logs.String() t.Logf("got logs: %s", logs) + // Verify all log messages are present for _, msg := range []string{"debug", "info", "warn", "error"} { want := `"msg":"` + msg + `"` if !strings.Contains(logs, want) { @@ -91,20 +96,9 @@ func (tc *TestCase) Run(ctx context.Context, t *testing.T) { } } - // Verify trace context is injected into each log line via GLS - lines := strings.Split(strings.TrimSpace(logs), "\n") - for _, line := range lines { - if line == "" { - continue - } - t.Logf("checking line: %s", line) - if ok, _ := regexp.MatchString(`"dd\.span_id":"\d+"`, line); !ok { - t.Errorf("no span ID in log line: %s", line) - } - if ok, _ := regexp.MatchString(`"dd\.trace_id":"[0-9a-f]+"`, line); !ok { - t.Errorf("no trace ID in log line: %s", line) - } - } + // Note: Automatic trace context injection is not currently supported for zap + // because zap's Write method does not receive a context.Context. + // Use WithTraceFields(ctx, logger) for manual trace injection. } func (*TestCase) ExpectedTraces() trace.Traces { diff --git a/orchestrion/all/orchestrion.tool.go b/orchestrion/all/orchestrion.tool.go index a03753ee47b..f0196d8f3ea 100644 --- a/orchestrion/all/orchestrion.tool.go +++ b/orchestrion/all/orchestrion.tool.go @@ -63,7 +63,6 @@ import ( _ "github.com/DataDog/dd-trace-go/contrib/twitchtv/twirp/v2" // integration _ "github.com/DataDog/dd-trace-go/contrib/valkey-io/valkey-go/v2" // integration _ "github.com/DataDog/dd-trace-go/contrib/valyala/fasthttp/v2" // integration - _ "github.com/DataDog/dd-trace-go/v2/contrib/go.uber.org/zap" // integration _ "github.com/DataDog/dd-trace-go/v2/contrib/os" // integration _ "github.com/DataDog/dd-trace-go/v2/ddtrace/tracer" // integration _ "github.com/DataDog/dd-trace-go/v2/orchestrion" // integration