Skip to content

[BUG]: Default baggage propagation style is inconsistent with docs #3466

Open
@benweint

Description

@benweint

Tracer Version(s)

1.72.2

Go Version(s)

go version go1.24.0 darwin/arm64

Bug Report

Summary

When propagating baggage, dd-trace-go's default behavior diverges from the Datadog documentation, using multiple ot-baggage-* headers instead of a single baggage header per the W3C spec the docs claim to follow.

Details

The documentation here says:

Baggage
Currently available in Python, Ruby, PHP, Java, Node.js, C++, Go and .NET. For other languages, please reach out to Support

By default, Baggage is automatically propagated through a distributed request using OpenTelemetry’s W3C-compatible headers.

The linked to W3C spec specifies baggage propagation via the baggage HTTP header. However, dd-trace-go actually appears to use the older ot-baggage- header prefix style, and there doesn't appear to be a way to convince it to do otherwise.

Why's it matter?

In addition to cross-tracing-system interoperability issues, the ot-baggage-style headers have a drawback when used in combination with dd-trace-py on the receiving end: because of how Python's WSGI normalizes -s in header names to _s, dd-trace-py cannot tell the difference between some-key and some_key. This can lead to confusion when the callee is expecting the former but gets the latter.

Using the newer single baggage would address this, because baggage item names appear in the HTTP header value, not the header name.

Contrast to dd-trace-py

dd-trace-py follows the docs and uses baggage by default.

Reproduction Code

main.go:

package main

import (
	"context"
	"log"
	"net/http"

	httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http"
	"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
)

func main() {
	tracer.Start(
		tracer.WithServiceName("ddtest"),
		tracer.WithEnv("local"),
	)
	defer tracer.Stop()

	ctx := context.Background()
	span, ctx := tracer.StartSpanFromContext(ctx, "main")
	defer span.Finish()

	span.SetBaggageItem("test-baggage-key", "test-baggage-value")

	client := httptrace.WrapClient(&http.Client{})
	req, err := http.NewRequestWithContext(ctx, "GET", "https://datadoghq.com", nil)
	if err != nil {
		log.Fatalf("Error creating request: %v", err)
	}
	_, err = client.Do(req)
	if err != nil {
		log.Fatalf("Error making request: %v", err)
	}

	tracer.Flush()
}

Run like this to demonstrate which header is being used:

❯ GODEBUG=http2debug=1 go run . 2>&1 | grep -i baggage 
2025/04/25 20:46:39 http2: Transport encoding header "ot-baggage-test-baggage-key" = "test-baggage-value"
2025/04/25 20:46:39 http2: Transport encoding header "ot-baggage-test-baggage-key" = "test-baggage-value"

Note that alternate values for DD_TRACE_PROPAGATION_STYLE as described here do not result in baggage being used either:

  • DD_TRACE_PROPAGATION_STYLE=datadog -> ot-baggage-* headers
  • DD_TRACE_PROPAGATION_STYLE=tracecontext -> no baggage headers
  • DD_TRACE_PROPAGATION_SYTLE=b3multi -> no baggage headers
  • DD_TRACE_PROPAGATION_STYLE=none -> no baggage headers

Error Logs

No response

Go Env Output

No response

Metadata

Metadata

Assignees

Labels

bugunintended behavior that has to be fixed

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions