Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions contrib/go.uber.org/zap/core.go
Original file line number Diff line number Diff line change
@@ -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()
}
31 changes: 31 additions & 0 deletions contrib/go.uber.org/zap/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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 "github.com/DataDog/dd-trace-go/v2/contrib/go.uber.org/zap"
"github.com/DataDog/dd-trace-go/v2/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")
}
35 changes: 35 additions & 0 deletions contrib/go.uber.org/zap/orchestrion.yml
Original file line number Diff line number Diff line change
@@ -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 }})
60 changes: 60 additions & 0 deletions contrib/go.uber.org/zap/zap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// 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 "github.com/DataDog/dd-trace-go/v2/contrib/go.uber.org/zap"

import (
"context"
"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/instrumentation"
"github.com/DataDog/dd-trace-go/v2/instrumentation/options"

"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() {
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 {
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)
}

spanID := strconv.FormatUint(span.Context().SpanID(), 10)

return logger.With(
zap.String(ext.LogKeyTraceID, traceID),
zap.String(ext.LogKeySpanID, spanID),
)
}
86 changes: 86 additions & 0 deletions contrib/go.uber.org/zap/zap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// 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"

"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.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")

// 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
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]

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)
}
1 change: 1 addition & 0 deletions ddtrace/tracer/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ var contribIntegrations = map[string]struct {
"github.com/uptrace/bun": {"Bun", false},
"github.com/urfave/negroni": {"Negroni", false},
"github.com/valyala/fasthttp": {"FastHTTP", false},
"go.uber.org/zap": {"go.uber.org/zap", false},
"github.com/valkey-io/valkey-go": {"Valkey", false},
}

Expand Down
2 changes: 1 addition & 1 deletion ddtrace/tracer/option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,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)
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,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
Expand Down Expand Up @@ -99,7 +100,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
Expand Down
5 changes: 5 additions & 0 deletions instrumentation/instrumentation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
5 changes: 5 additions & 0 deletions instrumentation/packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions internal/env/supported_configurations.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions internal/env/supported_configurations.json
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,9 @@
"DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH": [
"A"
],
"DD_TRACE_ZAP_ANALYTICS_ENABLED": [
"A"
],
"DD_TRACE__ANALYTICS_ENABLED": [
"A"
],
Expand Down
18 changes: 18 additions & 0 deletions internal/orchestrion/_integration/zap/generated_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading