Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package datadog.opentelemetry.shim.baggage;

import static java.util.stream.Collectors.toMap;

import io.opentelemetry.api.baggage.Baggage;
import io.opentelemetry.api.baggage.BaggageBuilder;
import io.opentelemetry.api.baggage.BaggageEntry;
import io.opentelemetry.api.baggage.BaggageEntryMetadata;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;

@ParametersAreNonnullByDefault
public class OtelBaggage implements Baggage {
private final datadog.trace.bootstrap.instrumentation.api.Baggage delegate;

public OtelBaggage(datadog.trace.bootstrap.instrumentation.api.Baggage delegate) {
this.delegate = delegate;
}

public OtelBaggage(Map<String, String> items) {
this(datadog.trace.bootstrap.instrumentation.api.Baggage.create(items));
}

@Override
public int size() {
return delegate.asMap().size();
}

@Override
public void forEach(BiConsumer<? super String, ? super BaggageEntry> consumer) {
for (Map.Entry<String, String> entry : delegate.asMap().entrySet()) {
consumer.accept(entry.getKey(), new ValueOnly(entry));
}
}

@Override
public Map<String, BaggageEntry> asMap() {
return delegate.asMap().entrySet().stream().collect(toMap(Map.Entry::getKey, ValueOnly::new));
}

@Nullable
@Override
public String getEntryValue(String key) {
return delegate.asMap().get(key);
}

@Nullable
@Override
public BaggageEntry getEntry(String key) {
String value = getEntryValue(key);
return value != null ? new ValueOnly(value) : null;
}

@Override
public BaggageBuilder toBuilder() {
return new OtelBaggageBuilder(delegate.asMap());
}

public datadog.trace.bootstrap.instrumentation.api.Baggage asAgentBaggage() {
return delegate;
}
Comment on lines +62 to +64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ This isn't a function that OTel has in their spec right?

Copy link
Contributor Author

@mcculls mcculls Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct - it is used to access the underlying Datadog baggage when the wrapper is "made current" using the OTel context API (i.e. for round-tripping purposes)

PS. the name is deliberate to match the asAgentSpan method in the OtelSpan wrapper.


static class ValueOnly implements BaggageEntry {
private final String value;

ValueOnly(String value) {
this.value = value;
}

ValueOnly(Map.Entry<String, String> entry) {
this(entry.getValue());
}

@Override
public String getValue() {
return value;
}

@Override
public BaggageEntryMetadata getMetadata() {
return BaggageEntryMetadata.empty();
}

@Override
public int hashCode() {
return Objects.hashCode(value);
}

@Override
public final boolean equals(Object o) {
return (o instanceof ValueOnly) && Objects.equals(value, ((ValueOnly) o).value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package datadog.opentelemetry.shim.baggage;

import io.opentelemetry.api.baggage.Baggage;
import io.opentelemetry.api.baggage.BaggageBuilder;
import io.opentelemetry.api.baggage.BaggageEntryMetadata;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;

@ParametersAreNonnullByDefault
public class OtelBaggageBuilder implements BaggageBuilder {
private final Map<String, String> items;

public OtelBaggageBuilder(Map<String, String> items) {
this.items = new HashMap<>(items);
}

@Override
public BaggageBuilder put(
@Nullable String key, @Nullable String value, BaggageEntryMetadata ignore) {
if (key != null && value != null) {
items.put(key, value);
}
return this;
}

@Override
public BaggageBuilder remove(@Nullable String key) {
if (key != null) {
items.remove(key);
}
return this;
}

@Override
public Baggage build() {
return new OtelBaggage(items);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package datadog.opentelemetry.shim.context;

import datadog.opentelemetry.shim.baggage.OtelBaggage;
import datadog.opentelemetry.shim.trace.OtelSpan;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.AttachableWrapper;
import datadog.trace.bootstrap.instrumentation.api.Baggage;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.context.Scope;
Expand All @@ -18,6 +20,7 @@ public class OtelContext implements Context {
/** Overridden root context. */
public static final Context ROOT = new OtelContext(datadog.context.Context.root());

private static final String OTEL_CONTEXT_BAGGAGE_KEY = "opentelemetry-baggage-key";
private static final String OTEL_CONTEXT_SPAN_KEY = "opentelemetry-trace-span-key";
private static final String OTEL_CONTEXT_ROOT_SPAN_KEY = "opentelemetry-traces-local-root-span";

Expand Down Expand Up @@ -55,6 +58,12 @@ public <V> V get(ContextKey<V> key) {
return (V) toOtelSpan(span.getLocalRootSpan());
}
// fall-through and check for non-datadog span data
} else if (OTEL_CONTEXT_BAGGAGE_KEY.equals(key.toString())) {
Baggage baggage = Baggage.fromContext(delegate);
if (baggage != null) {
return (V) new OtelBaggage(baggage);
}
// fall-through and check for non-datadog baggage
}
return (V) delegate.get(delegateKey(key));
}
Expand All @@ -67,6 +76,12 @@ public <V> Context with(ContextKey<V> key, V value) {
return new OtelContext(delegate.with(span));
}
// fall-through and store as non-datadog span data
} else if (OTEL_CONTEXT_BAGGAGE_KEY.equals(key.toString())) {
if (value instanceof OtelBaggage) {
Baggage baggage = ((OtelBaggage) value).asAgentBaggage();
return new OtelContext(delegate.with(baggage));
}
// fall-through and store as non-datadog baggage
}
return new OtelContext(delegate.with(delegateKey(key), value));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ public String[] helperClassNames() {
"datadog.opentelemetry.shim.context.propagation.AgentTextMapPropagator",
"datadog.opentelemetry.shim.context.propagation.OtelContextPropagators",
"datadog.opentelemetry.shim.context.propagation.TraceStateHelper",
"datadog.opentelemetry.shim.baggage.OtelBaggage",
"datadog.opentelemetry.shim.baggage.OtelBaggage$ValueOnly",
"datadog.opentelemetry.shim.baggage.OtelBaggageBuilder",
"datadog.opentelemetry.shim.trace.OtelExtractedContext",
"datadog.opentelemetry.shim.trace.OtelConventions",
"datadog.opentelemetry.shim.trace.OtelConventions$1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ public String[] helperClassNames() {
return new String[] {
"datadog.opentelemetry.shim.context.OtelContext",
"datadog.opentelemetry.shim.context.OtelScope",
"datadog.opentelemetry.shim.baggage.OtelBaggage",
"datadog.opentelemetry.shim.baggage.OtelBaggage$ValueOnly",
"datadog.opentelemetry.shim.baggage.OtelBaggageBuilder",
"datadog.opentelemetry.shim.trace.OtelExtractedContext",
"datadog.opentelemetry.shim.trace.OtelConventions",
"datadog.opentelemetry.shim.trace.OtelConventions$1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ public String[] helperClassNames() {
return new String[] {
"datadog.opentelemetry.shim.context.OtelContext",
"datadog.opentelemetry.shim.context.OtelScope",
"datadog.opentelemetry.shim.baggage.OtelBaggage",
"datadog.opentelemetry.shim.baggage.OtelBaggage$ValueOnly",
"datadog.opentelemetry.shim.baggage.OtelBaggageBuilder",
"datadog.opentelemetry.shim.trace.OtelExtractedContext",
"datadog.opentelemetry.shim.trace.OtelConventions",
"datadog.opentelemetry.shim.trace.OtelConventions$1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package opentelemetry14.context
import datadog.trace.agent.test.InstrumentationSpecification
import datadog.trace.api.DDSpanId
import io.opentelemetry.api.GlobalOpenTelemetry
import io.opentelemetry.api.baggage.Baggage
import io.opentelemetry.api.trace.Span
import io.opentelemetry.context.Context
import io.opentelemetry.context.ContextKey
Expand Down Expand Up @@ -324,6 +325,64 @@ class ContextTest extends InstrumentationSpecification {
context.get(CustomData.KEY) == null
}

def "test Baggage.current/makeCurrent()"() {
when:
def otelBaggage = Baggage.current()
def otelBaggageFromContext = Baggage.fromContext(Context.current())
def otelBaggageFromContextOrNull = Baggage.fromContextOrNull(Context.current())

then: "current baggage must be empty or null"
otelBaggage != null
otelBaggage.isEmpty()
otelBaggageFromContext != null
otelBaggageFromContext.isEmpty()
otelBaggageFromContextOrNull == null

when:
def ddContext = datadog.context.Context.current()
def ddBaggage = datadog.trace.bootstrap.instrumentation.api.Baggage.create([
"foo" : "value_to_be_replaced",
"FOO" : "UNTOUCHED",
"remove_me_key" : "remove_me_value"
])
def ddScope = ddContext.with(ddBaggage).attach()
otelBaggage = Baggage.current()
otelBaggageFromContext = Baggage.fromContext(Context.current())
otelBaggageFromContextOrNull = Baggage.fromContextOrNull(Context.current())

then: "Datadog baggage must be current"
otelBaggage != null
otelBaggage.size() == 3
otelBaggage.getEntryValue("foo") == "value_to_be_replaced"
otelBaggage.getEntryValue("FOO") == "UNTOUCHED"
otelBaggage.getEntryValue("remove_me_key") == "remove_me_value"
otelBaggage.asMap() == otelBaggageFromContext.asMap()
otelBaggage.asMap() == otelBaggageFromContextOrNull.asMap()

when:
def builder = otelBaggage.toBuilder()
builder.put("new_foo", "new_value")
builder.put("foo", "overwrite_value")
builder.remove("remove_me_key")
def otelScope = builder.build().makeCurrent()
otelBaggage = Baggage.current()
otelBaggageFromContext = Baggage.fromContext(Context.current())
otelBaggageFromContextOrNull = Baggage.fromContextOrNull(Context.current())

then: "baggage must contain OTel changes"
otelBaggage != null
otelBaggage.size() == 3
otelBaggage.getEntryValue("foo") == "overwrite_value"
otelBaggage.getEntryValue("FOO") == "UNTOUCHED"
otelBaggage.getEntryValue("new_foo") == "new_value"
otelBaggage.asMap() == otelBaggageFromContext.asMap()
otelBaggage.asMap() == otelBaggageFromContextOrNull.asMap()

cleanup:
otelScope.close()
ddScope.close()
}

@Override
void cleanup() {
// Test for context leak
Expand Down