Skip to content
Open
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
9 changes: 9 additions & 0 deletions cmd/root/otel.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
Expand Down Expand Up @@ -65,6 +66,14 @@ func initOTelSDK(ctx context.Context) (err error) {
tp := trace.NewTracerProvider(tracerProviderOpts...)
otel.SetTracerProvider(tp)

// Propagator must be set so otelhttp injects W3C traceparent on
// outbound requests and extracts it from incoming ones. Without this
// the SDK records spans locally but they never chain across services.
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
))

go func() {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ require (
github.com/yuin/goldmark-emoji v1.0.5 // indirect
go.etcd.io/bbolt v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect
Expand Down
29 changes: 26 additions & 3 deletions pkg/httpclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import (
"maps"
"net/http"
"net/url"
"os"
"runtime"

"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"

"github.com/docker/docker-agent/pkg/remote"
"github.com/docker/docker-agent/pkg/version"
)
Expand Down Expand Up @@ -97,18 +100,38 @@ func WithQuery(query url.Values) Opt {
}
}

// newTransport returns an HTTP transport with automatic gzip compression disabled and using Docker Desktop proxy if available.
// newTransport returns an HTTP transport with automatic gzip compression
// disabled and using Docker Desktop proxy if available.
//
// When OpenTelemetry is enabled (i.e. OTEL_EXPORTER_OTLP_ENDPOINT is set,
// matching the gating in initOTelSDK), the transport is wrapped with
// otelhttp so each outbound request emits a CLIENT span and the W3C
// traceparent header is injected. When OTel is disabled, the bare
// transport is returned so we don't allocate per-request spans nor send
// a traceparent header to upstream LLM providers.
func newTransport(ctx context.Context) http.RoundTripper {
// Get the base transport with Desktop proxy support from remote package
rt := remote.NewTransport(ctx)

// If it's an http.Transport, disable compression for SSE streaming compatibility
if transport, ok := rt.(*http.Transport); ok {
transport.DisableCompression = true
return transport
rt = transport
}

return rt
return WrapWithOTel(rt)
}

// WrapWithOTel returns rt wrapped with otelhttp when OpenTelemetry is
// enabled (OTEL_EXPORTER_OTLP_ENDPOINT set, matching the gating in
// cmd/root/otel.go), or rt unchanged otherwise. Exposed so callers that
// build their own transports outside of NewHTTPClient can opt into the
// same env-gated instrumentation without duplicating the gating logic.
func WrapWithOTel(rt http.RoundTripper) http.RoundTripper {
if os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") == "" {
return rt
}
return otelhttp.NewTransport(rt)
}

type userAgentTransport struct {
Expand Down
Loading