diff --git a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy index ed077d10621..fba553fe199 100644 --- a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy +++ b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy @@ -39,6 +39,7 @@ import datadog.trace.bootstrap.instrumentation.api.Tags import datadog.trace.bootstrap.instrumentation.api.URIDataAdapter import datadog.trace.bootstrap.instrumentation.api.URIUtils import datadog.trace.core.DDSpan +import datadog.trace.core.Metadata import datadog.trace.core.datastreams.StatsGroup import datadog.trace.test.util.Flaky import groovy.json.JsonOutput @@ -778,6 +779,62 @@ abstract class HttpServerTest extends WithHttpServer { 'GET' | null | 'x-datadog-test-request-header' | 'bar' | ['request_header_tag': 'bar'] } + def "test baggage span tags are properly added"() { + setup: + def recordedBaggageTags = [:] + TEST_WRITER.metadataConsumer = { Metadata md -> + md.baggage.forEach { k, v -> + // record non-internal baggage sent to agent as trace metadata + if (!k.startsWith("_dd.")) { + recordedBaggageTags.put(k, v) + } + } + } + // Use default configuration for TRACE_BAGGAGE_TAG_KEYS (user.id, session.id, account.id) + def baggageHeader = "user.id=test-user,session.id=test-session,account.id=test-account,language=en" + def request = request(SUCCESS, 'GET', null) + .header("baggage", baggageHeader) + .build() + def response = client.newCall(request).execute() + if (isDataStreamsEnabled()) { + TEST_DATA_STREAMS_WRITER.waitForGroups(1) + } + + expect: + response.code() == SUCCESS.status + response.body().string() == SUCCESS.body + + and: + assertTraces(1) { + trace(spanCount(SUCCESS)) { + sortSpansByStart() + // Verify baggage tags are added for default configured keys only + serverSpan(it, null, null, 'GET', SUCCESS) + if (hasHandlerSpan()) { + handlerSpan(it) + } + controllerSpan(it) + if (hasResponseSpan(SUCCESS)) { + responseSpan(it, SUCCESS) + } + } + } + recordedBaggageTags == [ + "baggage.user.id": "test-user", + "baggage.session.id": "test-session", + "baggage.account.id": "test-account" + // "baggage.language" should NOT be present since it's not in default config + ] + + and: + if (isDataStreamsEnabled()) { + StatsGroup first = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == 0 } + verifyAll(first) { + tags == DSM_EDGE_TAGS + } + } + } + @Flaky(value = "https://github.com/DataDog/dd-trace-java/issues/4690", suites = ["MuleHttpServerForkedTest"]) def "test QUERY_ENCODED_BOTH with response header x-ig-response-header tag mapping"() { setup: diff --git a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java index ab052ca6707..ee56efdd8bf 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java @@ -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 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; diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/TracerConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/TracerConfig.java index d817c88666e..0300cef2070 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/TracerConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/TracerConfig.java @@ -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"; diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/ListWriter.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/ListWriter.java index 03bed3c7174..66f9f468624 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/ListWriter.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/ListWriter.java @@ -26,6 +26,8 @@ public class ListWriter extends CopyOnWriteArrayList> implements Wr private Filter filter = ACCEPT_ALL; + private MetadataConsumer metadataConsumer = MetadataConsumer.NO_OP; + public List firstTrace() { return get(0); } @@ -38,7 +40,7 @@ public void write(List trace) { for (DDSpan span : trace) { // This is needed to properly do all delayed processing to make this writer even // remotely realistic so the test actually test something - span.processTagsAndBaggage(MetadataConsumer.NO_OP); + span.processTagsAndBaggage(metadataConsumer); } add(trace); @@ -117,6 +119,11 @@ public void setFilter(Filter filter) { this.filter = filter; } + /** Set a {@link MetadataConsumer} to capture what trace metadata would be sent to the agent. */ + public void setMetadataConsumer(MetadataConsumer metadataConsumer) { + this.metadataConsumer = metadataConsumer; + } + private boolean isReported(DDSpan span) { for (List trace : this) { for (DDSpan aSpan : trace) { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java index a5cc89f5011..6b5ea0b5f10 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java @@ -59,6 +59,7 @@ import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.bootstrap.instrumentation.api.Baggage; import datadog.trace.bootstrap.instrumentation.api.BlackHoleSpan; import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration; import datadog.trace.bootstrap.instrumentation.api.SpanAttributes; @@ -1591,6 +1592,7 @@ private DDSpanContext buildSpanContext() { final long spanId; final long parentSpanId; final Map baggage; + final Baggage w3cBaggage; final TraceCollector parentTraceCollector; final int samplingPriority; final CharSequence origin; @@ -1645,6 +1647,7 @@ private DDSpanContext buildSpanContext() { traceId = ddsc.getTraceId(); parentSpanId = ddsc.getSpanId(); baggage = ddsc.getBaggageItems(); + w3cBaggage = null; parentTraceCollector = ddsc.getTraceCollector(); samplingPriority = PrioritySampling.UNSET; origin = null; @@ -1706,6 +1709,7 @@ private DDSpanContext buildSpanContext() { coreTagsNeedsIntercept = true; // maybe intercept isn't needed? origin = tc.getOrigin(); baggage = tc.getBaggage(); + w3cBaggage = tc.getW3CBaggage(); requestContextDataAppSec = tc.getRequestContextDataAppSec(); requestContextDataIast = tc.getRequestContextDataIast(); ciVisibilityContextData = tc.getCiVisibilityContextData(); @@ -1715,6 +1719,7 @@ private DDSpanContext buildSpanContext() { coreTagsNeedsIntercept = false; origin = null; baggage = null; + w3cBaggage = null; requestContextDataAppSec = null; requestContextDataIast = null; ciVisibilityContextData = null; @@ -1805,6 +1810,7 @@ private DDSpanContext buildSpanContext() { samplingPriority, origin, baggage, + w3cBaggage, errorFlag, spanType, tagsSize, diff --git a/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java b/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java index 482d1efe09e..ed18aa1efc8 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java @@ -5,6 +5,7 @@ import static datadog.trace.api.cache.RadixTreeCache.HTTP_STATUSES; import static datadog.trace.bootstrap.instrumentation.api.ErrorPriorities.UNSET; +import datadog.trace.api.Config; import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTags; import datadog.trace.api.DDTraceId; @@ -23,6 +24,7 @@ import datadog.trace.api.sampling.SamplingMechanism; import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; +import datadog.trace.bootstrap.instrumentation.api.Baggage; import datadog.trace.bootstrap.instrumentation.api.ProfilerContext; import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration; import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities; @@ -77,6 +79,8 @@ public class DDSpanContext /** Baggage is associated with the whole trace and shared with other spans */ private volatile Map baggageItems; + private final Baggage w3cBaggage; + // Not Shared with other span contexts private final DDTraceId traceId; private final long spanId; @@ -187,6 +191,7 @@ public DDSpanContext( samplingPriority, origin, baggageItems, + null, errorFlag, spanType, tagsSize, @@ -233,6 +238,7 @@ public DDSpanContext( samplingPriority, origin, baggageItems, + null, errorFlag, spanType, tagsSize, @@ -279,6 +285,7 @@ public DDSpanContext( samplingPriority, origin, baggageItems, + null, errorFlag, spanType, tagsSize, @@ -304,6 +311,7 @@ public DDSpanContext( final int samplingPriority, final CharSequence origin, final Map baggageItems, + final Baggage w3cBaggage, final boolean errorFlag, final CharSequence spanType, final int tagsSize, @@ -331,6 +339,7 @@ public DDSpanContext( } else { this.baggageItems = new ConcurrentHashMap<>(baggageItems); } + this.w3cBaggage = w3cBaggage; this.requestContextDataAppSec = requestContextDataAppSec; this.requestContextDataIast = requestContextDataIast; @@ -926,6 +935,9 @@ public void processTagsAndBaggage( Map baggageItemsWithPropagationTags; if (injectBaggageAsTags) { baggageItemsWithPropagationTags = new HashMap<>(baggageItems); + if (w3cBaggage != null) { + injectW3CBaggageTags(baggageItemsWithPropagationTags); + } propagationTags.fillTagMap(baggageItemsWithPropagationTags); } else { baggageItemsWithPropagationTags = propagationTags.createTagMap(); @@ -948,6 +960,27 @@ public void processTagsAndBaggage( } } + void injectW3CBaggageTags(Map baggageItemsWithPropagationTags) { + List baggageTagKeys = Config.get().getTraceBaggageTagKeys(); + if (baggageTagKeys.isEmpty()) { + return; + } + Map w3cBaggageItems = w3cBaggage.asMap(); + for (String key : baggageTagKeys) { + if ("*".equals(key)) { + // If the key is "*", we add all baggage items + for (Map.Entry entry : w3cBaggageItems.entrySet()) { + baggageItemsWithPropagationTags.put("baggage." + entry.getKey(), entry.getValue()); + } + break; + } + String value = w3cBaggageItems.get(key); + if (value != null) { + baggageItemsWithPropagationTags.put("baggage." + key, value); + } + } + } + @Override public void setIntegrationName(CharSequence integrationName) { this.integrationName = integrationName; diff --git a/dd-trace-core/src/main/java/datadog/trace/core/baggage/BaggagePropagator.java b/dd-trace-core/src/main/java/datadog/trace/core/baggage/BaggagePropagator.java index 3f3d00c373f..f3957dbc003 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/baggage/BaggagePropagator.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/baggage/BaggagePropagator.java @@ -7,7 +7,10 @@ import datadog.context.propagation.CarrierVisitor; import datadog.context.propagation.Propagator; import datadog.trace.api.Config; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; import datadog.trace.bootstrap.instrumentation.api.Baggage; +import datadog.trace.bootstrap.instrumentation.api.TagContext; import datadog.trace.core.util.PercentEscaper; import datadog.trace.core.util.PercentEscaper.Escaped; import java.io.UnsupportedEncodingException; @@ -109,7 +112,21 @@ public Context extract(Context context, C carrier, CarrierVisitor visitor } BaggageExtractor baggageExtractor = new BaggageExtractor(); visitor.forEachKeyValue(carrier, baggageExtractor); - return baggageExtractor.extracted == null ? context : context.with(baggageExtractor.extracted); + Baggage baggage = baggageExtractor.extracted; + if (baggage == null) { + return context; + } + + // TODO: consider a better way to link baggage with the extracted (legacy) TagContext + AgentSpan extractedSpan = AgentSpan.fromContext(context); + if (extractedSpan != null) { + AgentSpanContext extractedSpanContext = extractedSpan.context(); + if (extractedSpanContext instanceof TagContext) { + ((TagContext) extractedSpanContext).setW3CBaggage(baggage); + } + } + + return context.with(baggage); } private class BaggageExtractor implements BiConsumer { diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/test/DDCoreSpecification.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/test/DDCoreSpecification.groovy index 7060b7a5084..5318c9b93c6 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/test/DDCoreSpecification.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/test/DDCoreSpecification.groovy @@ -70,6 +70,7 @@ abstract class DDCoreSpecification extends DDSpecification { prioritySampling, null, [:], + null, false, spanType, 0, diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index 5e08f1f9c64..ba79642bd06 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -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; @@ -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; @@ -823,6 +825,7 @@ public static String getHostName() { private final boolean tracePropagationExtractFirst; private final int traceBaggageMaxItems; private final int traceBaggageMaxBytes; + private final List traceBaggageTagKeys; private final int clockSyncPeriod; private final boolean logsInjectionEnabled; @@ -1694,6 +1697,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 = @@ -2966,6 +2974,10 @@ public Map getBaggageMapping() { return baggageMapping; } + public List getTraceBaggageTagKeys() { + return traceBaggageTagKeys; + } + public Map getHttpServerPathResourceNameMapping() { return httpServerPathResourceNameMapping; } @@ -5370,6 +5382,8 @@ public String toString() { + traceKeepLatencyThreshold + ", traceStrictWritesEnabled=" + traceStrictWritesEnabled + + ", traceBaggageTagKeys=" + + traceBaggageTagKeys + ", tracePropagationStylesToExtract=" + tracePropagationStylesToExtract + ", tracePropagationStylesToInject=" diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentPropagation.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentPropagation.java index 3dea68cd5b2..fba659bea74 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentPropagation.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentPropagation.java @@ -14,7 +14,9 @@ public final class AgentPropagation { public static final Concern TRACING_CONCERN = named("tracing"); - public static final Concern BAGGAGE_CONCERN = named("baggage"); + // TODO: Baggage propagator should run after tracing so it can link baggage with the span context + // TODO: remove this priority once we have a story for replacing TagContext with the Context API + public static final Concern BAGGAGE_CONCERN = withPriority("baggage", 105); public static final Concern XRAY_TRACING_CONCERN = named("tracing-xray"); // TODO DSM propagator should run after the other propagators as it stores the pathway context diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java index 9a22acaa8cb..6cc277e3b22 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java @@ -32,6 +32,7 @@ public class TagContext implements AgentSpanContext.Extracted { private PathwayContext pathwayContext; private final HttpHeaders httpHeaders; private final Map baggage; + private Baggage w3cBaggage; private final int samplingPriority; private final TraceConfig traceConfig; private final TracePropagationStyle propagationStyle; @@ -189,6 +190,14 @@ public Iterable> baggageItems() { return baggage.entrySet(); } + public final Baggage getW3CBaggage() { + return w3cBaggage; + } + + public void setW3CBaggage(Baggage w3cBaggage) { + this.w3cBaggage = w3cBaggage; + } + @Override public DDTraceId getTraceId() { return traceId;