Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d06652d
WIP: baggage span tags
rachelyangdog Jul 14, 2025
bb005f5
simple test
rachelyangdog Jul 14, 2025
2f2b83c
Merge branch 'master' into rachel.yang/baggage-span-tags
rachelyangdog Jul 14, 2025
22631c5
fix build errors
rachelyangdog Jul 15, 2025
87e2aaa
adding more tests
rachelyangdog Jul 15, 2025
20885c9
Merge branch 'master' into rachel.yang/baggage-span-tags
rachelyangdog Jul 15, 2025
2ca1604
Add baggage span tags functionality and tests
rachelyangdog Jul 16, 2025
de8a55a
Move baggage tags feature to HttpServerDecorator.getExtractedSpanCont…
mcculls Jul 18, 2025
5b52ea4
Fix potential corner-case where TagContext.getTags() may return an im…
mcculls Jul 18, 2025
af0a640
Move baggage tags unit test to HttpServerDecoratorTest
mcculls Jul 18, 2025
eca080e
Baggage tags unit tests have been moved to HttpServerDecoratorTest
mcculls Jul 18, 2025
2d45a1f
Merge remote-tracking branch 'origin/master' into rachel.yang/baggage…
mcculls Jul 22, 2025
85d573f
Move W3C baggage tag injection to just before the span is serialized
mcculls Jul 22, 2025
33fe83e
Update test trace writer so injected baggage metadata can be asserted…
mcculls Jul 22, 2025
3a8d4a8
spotless format
mcculls Jul 22, 2025
a4fb5a6
Remove unused method
mcculls Jul 22, 2025
988bed8
Fix test code
mcculls Jul 22, 2025
dd9bbdd
Merge remote-tracking branch 'origin/master' into rachel.yang/baggage…
mcculls Jul 22, 2025
a22edab
Support recording of baggage, that would be sent to agent as trace me…
mcculls Jul 22, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,65 @@ abstract class OkHttp3Test extends HttpClientTest {
method = "GET"
url = server.address.resolve(path)
}

def "baggage span tags are properly added"() {
setup:
// W3C Baggage header format: key1=value1,key2=value2,key3=value3
def baggageHeader = "user.id=bark,session.id=test-sess1,account.id=fetch,language=en"
def sentHeaders = [:]

when:
def status
// Capture the headers that OkHttp3 sends
def interceptor = new Interceptor() {
@Override
Response intercept(Chain chain) throws IOException {
def request = chain.request()
// Capture all headers sent
request.headers().names().each { headerName ->
sentHeaders[headerName] = request.header(headerName)
}
return chain.proceed(request)
}
}

def testClient = new OkHttpClient.Builder()
.connectTimeout(CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.readTimeout(READ_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.writeTimeout(READ_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.addInterceptor(interceptor)
.build()

def request = new Request.Builder()
.url(server.address.resolve("/success").toURL())
.header("baggage", baggageHeader) // Pass baggage directly in header
.get()
.build()
def response = testClient.newCall(request).execute()
status = response.code()

then:
status == 200
// Verify baggage header was sent
sentHeaders["baggage"] == baggageHeader
sentHeaders["baggage"].contains("user.id=bark")
sentHeaders["baggage"].contains("session.id=test-sess1")
sentHeaders["baggage"].contains("account.id=fetch")
sentHeaders["baggage"].contains("language=en")

// Verify the resulting span has the correct baggage tags (only default configured keys)
assertTraces(1) {
trace(1) {
clientSpan(it, null, "GET", false, false, server.address.resolve("/success"), 200, false, null, false, [
// Should have baggage tags for keys in default configuration
"baggage.user.id": "bark",
"baggage.session.id": "test-sess1",
"baggage.account.id": "fetch",
// "baggage.language" should NOT be present since it's not in default config
])
}
}
}
}

@Timeout(5)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ public final class ConfigDefaults {
new LinkedHashSet<>(asList(PropagationStyle.DATADOG));
static final int DEFAULT_TRACE_BAGGAGE_MAX_ITEMS = 64;
static final int DEFAULT_TRACE_BAGGAGE_MAX_BYTES = 8192;
static final List<String> DEFAULT_TRACE_BAGGAGE_TAG_KEYS =
Arrays.asList("user.id", "session.id", "account.id");
static final boolean DEFAULT_JMX_FETCH_ENABLED = true;
static final boolean DEFAULT_TRACE_AGENT_V05_ENABLED = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ public final class TracerConfig {
public static final String TRACE_PROPAGATION_EXTRACT_FIRST = "trace.propagation.extract.first";
public static final String TRACE_BAGGAGE_MAX_ITEMS = "trace.baggage.max.items";
public static final String TRACE_BAGGAGE_MAX_BYTES = "trace.baggage.max.bytes";
public static final String TRACE_BAGGAGE_TAG_KEYS = "trace.baggage.tag.keys";

public static final String ENABLE_TRACE_AGENT_V05 = "trace.agent.v0.5.enabled";

Expand Down
27 changes: 27 additions & 0 deletions dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java
Original file line number Diff line number Diff line change
Expand Up @@ -1604,6 +1604,7 @@ private DDSpanContext buildSpanContext() {
Object ciVisibilityContextData;
final PathwayContext pathwayContext;
final PropagationTags propagationTags;
final Map<String, String> baggageTags;

if (this.spanId == 0) {
spanId = tracer.idGenerationStrategy.generateSpanId();
Expand Down Expand Up @@ -1651,6 +1652,7 @@ private DDSpanContext buildSpanContext() {
coreTags = null;
coreTagsNeedsIntercept = false;
rootSpanTags = null;
baggageTags = null;
rootSpanTagsNeedsIntercept = false;
parentServiceName = ddsc.getServiceName();
if (serviceName == null) {
Expand Down Expand Up @@ -1706,6 +1708,7 @@ private DDSpanContext buildSpanContext() {
coreTagsNeedsIntercept = true; // maybe intercept isn't needed?
origin = tc.getOrigin();
baggage = tc.getBaggage();
baggageTags = mapBaggageTags(baggage);
requestContextDataAppSec = tc.getRequestContextDataAppSec();
requestContextDataIast = tc.getRequestContextDataIast();
ciVisibilityContextData = tc.getCiVisibilityContextData();
Expand All @@ -1718,6 +1721,7 @@ private DDSpanContext buildSpanContext() {
requestContextDataAppSec = null;
requestContextDataIast = null;
ciVisibilityContextData = null;
baggageTags = null;
}

rootSpanTags = tracer.localRootSpanTags;
Expand Down Expand Up @@ -1826,6 +1830,7 @@ private DDSpanContext buildSpanContext() {
context.setAllTags(coreTags, coreTagsNeedsIntercept);
context.setAllTags(rootSpanTags, rootSpanTagsNeedsIntercept);
context.setAllTags(contextualTags);
context.setAllTags(baggageTags);
return context;
}
}
Expand Down Expand Up @@ -1910,4 +1915,26 @@ static TagMap withTracerTags(
}
return result.freeze();
}

static Map<String, String> mapBaggageTags(Map<String, String> baggage) {
List<String> baggageTagKeys = Config.get().getTraceBaggageTagKeys();
if (baggageTagKeys.isEmpty()) {
return Collections.emptyMap();
}
Map<String, String> baggageTags = new HashMap<>(baggageTagKeys.size());
for (String key : baggageTagKeys) {
if (key == "*") {
// If the key is "*", we add all baggage items
for (Map.Entry<String, String> entry : baggage.entrySet()) {
baggageTags.put("baggage." + entry.getKey(), entry.getValue());
}
break;
}
String value = baggage.get(key);
if (value != null) {
baggageTags.put("baggage." + key, value);
}
}
return Collections.unmodifiableMap(baggageTags);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import static datadog.trace.api.DDTags.DJM_ENABLED
import static datadog.trace.api.DDTags.DSM_ENABLED
import static datadog.trace.api.DDTags.PROFILING_ENABLED
import static datadog.trace.api.DDTags.SCHEMA_VERSION_TAG_KEY
import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_TAG_KEYS

import datadog.trace.api.Config
import datadog.trace.api.DDSpanId
import datadog.trace.api.DDTraceId
import datadog.trace.api.TagMap
import datadog.trace.api.gateway.RequestContextSlot
import datadog.trace.api.sampling.PrioritySampling
import datadog.trace.api.naming.SpanNaming
import datadog.trace.api.sampling.PrioritySampling
import datadog.trace.bootstrap.instrumentation.api.AgentScope
Expand Down Expand Up @@ -492,6 +494,31 @@ class CoreSpanBuilderTest extends DDCoreSpecification {
span1.finish()
}

def "buildSpan should add baggage tags with different configurations"() {
setup:
injectSysConfig(TRACE_BAGGAGE_TAG_KEYS, baggageTagKeysConfig)
def baggage = ["user.id": "alice", "session.id": "123", "region": "us-west-1", "env": "production"]
def tagContext = new TagContext(null, null, null, baggage, PrioritySampling.UNSET, null, DATADOG, DDTraceId.ZERO)

when:
def span = tracer.buildSpan("test", "test-op")
.asChildOf(tagContext)
.start()

then:
// Filter span tags to only check baggage tags (those starting with "baggage.")
def actualBaggageTags = span.tags.findAll { key, value -> key.startsWith("baggage.") }
actualBaggageTags == expectedBaggageTags

cleanup:
span.finish()

where:
baggageTagKeysConfig | expectedBaggageTags
"user.id" | ["baggage.user.id": "alice"]
"user.id,session.id" | ["baggage.user.id": "alice", "baggage.session.id": "123"]
}

def productTags() {
def productTags = [
(PROFILING_ENABLED) : Config.get().isProfilingEnabled() ? 1 : 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import static datadog.trace.api.config.TracerConfig.HEADER_TAGS
import static datadog.trace.api.config.TracerConfig.PRIORITY_SAMPLING
import static datadog.trace.api.config.TracerConfig.SERVICE_MAPPING
import static datadog.trace.api.config.TracerConfig.SPAN_TAGS
import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_TAG_KEYS
import static datadog.trace.api.config.TracerConfig.WRITER_TYPE

@Timeout(10)
Expand Down Expand Up @@ -639,6 +640,33 @@ class CoreTracerTest extends DDCoreSpecification {
"service" | "env" | "service" | "env_1"
"service" | "env" | "service_2" | "env_2"
}

def "test mapBaggageTags default"() {
when:
def tags = CoreTracer.mapBaggageTags(["user.id": "doggo", "account.id": "test", "session.id":"1234", "region":"us-east-1"])
then:
tags == ["baggage.user.id": "doggo", "baggage.account.id": "test", "baggage.session.id": "1234"]
}

def "test mapBaggageTags with different configurations"() {
setup:
injectSysConfig(TRACE_BAGGAGE_TAG_KEYS, baggageTagKeysConfig)

when:
def tags = CoreTracer.mapBaggageTags(["user.id": "doggo", "foo": "bar", "language":"en"])

then:
tags == expectedTags

where:
baggageTagKeysConfig | expectedTags
"*" | ["baggage.user.id": "doggo", "baggage.foo": "bar", "baggage.language": "en"]
" " | [:]
"system.id" | [:]
"user.id" | ["baggage.user.id": "doggo"]
"foo" | ["baggage.foo": "bar"]
"user.id,foo" | ["baggage.user.id": "doggo", "baggage.foo": "bar"]
}
}

class WriterWithExplicitFlush implements datadog.trace.common.writer.Writer {
Expand Down
14 changes: 14 additions & 0 deletions internal-api/src/main/java/datadog/trace/api/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@
import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_ANALYTICS_ENABLED;
import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_BAGGAGE_MAX_BYTES;
import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_BAGGAGE_MAX_ITEMS;
import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_BAGGAGE_TAG_KEYS;
import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_CLOUD_PAYLOAD_TAGGING_SERVICES;
import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_EXPERIMENTAL_FEATURES_ENABLED;
import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_HTTP_RESOURCE_REMOVE_TRAILING_SLASH;
Expand Down Expand Up @@ -583,6 +584,7 @@
import static datadog.trace.api.config.TracerConfig.TRACE_ANALYTICS_ENABLED;
import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_MAX_BYTES;
import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_MAX_ITEMS;
import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_TAG_KEYS;
import static datadog.trace.api.config.TracerConfig.TRACE_CLIENT_IP_HEADER;
import static datadog.trace.api.config.TracerConfig.TRACE_CLIENT_IP_RESOLVER_ENABLED;
import static datadog.trace.api.config.TracerConfig.TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH;
Expand Down Expand Up @@ -823,6 +825,7 @@ public static String getHostName() {
private final boolean tracePropagationExtractFirst;
private final int traceBaggageMaxItems;
private final int traceBaggageMaxBytes;
private final List<String> traceBaggageTagKeys;
private final int clockSyncPeriod;
private final boolean logsInjectionEnabled;

Expand Down Expand Up @@ -1695,6 +1698,11 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins
// If we have a new setting, we log a warning
logOverriddenDeprecatedSettingWarning(PROPAGATION_STYLE_INJECT, injectOrigin, inject);
}

// Parse the baggage tag keys configuration
traceBaggageTagKeys =
configProvider.getList(TRACE_BAGGAGE_TAG_KEYS, DEFAULT_TRACE_BAGGAGE_TAG_KEYS);

// Now we can check if we should pick the default injection/extraction

tracePropagationStylesToExtract =
Expand Down Expand Up @@ -2966,6 +2974,10 @@ public Map<String, String> getBaggageMapping() {
return baggageMapping;
}

public List<String> getTraceBaggageTagKeys() {
return traceBaggageTagKeys;
}

public Map<String, String> getHttpServerPathResourceNameMapping() {
return httpServerPathResourceNameMapping;
}
Expand Down Expand Up @@ -5374,6 +5386,8 @@ public String toString() {
+ traceKeepLatencyThreshold
+ ", traceStrictWritesEnabled="
+ traceStrictWritesEnabled
+ ", traceBaggageTagKeys="
+ traceBaggageTagKeys
+ ", tracePropagationStylesToExtract="
+ tracePropagationStylesToExtract
+ ", tracePropagationStylesToInject="
Expand Down