Skip to content

Migrate OtelContext wrapper to new internal Context API #8645

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 1, 2025
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
@@ -1,170 +1,109 @@
package datadog.opentelemetry.shim.context;

import datadog.opentelemetry.shim.trace.OtelSpan;
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
import datadog.trace.bootstrap.instrumentation.api.AttachableWrapper;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.context.Scope;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;

@SuppressWarnings({"rawtypes", "unchecked"})
@ParametersAreNonnullByDefault
public class OtelContext implements Context {
private static final Object[] NO_ENTRIES = {};

/** Overridden root context. */
public static final Context ROOT = new OtelContext(OtelSpan.invalid(), OtelSpan.invalid());
public static final Context ROOT = new OtelContext(datadog.context.Context.root());

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";

/** Keep track of propagated context that has not been captured on the scope stack. */
private static final ThreadLocal<OtelContext> lastPropagated = new ThreadLocal<>();
/** Records the keys needed to access the delegate context, mapped by key name. */
private static final Map<ContextKey<?>, datadog.context.ContextKey<?>> DELEGATE_KEYS =
Copy link
Collaborator

Choose a reason for hiding this comment

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

I imagine that this is not that big. However I did not see any cleanup or expiry but only entry insertion. Is that on purpose?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct, this is never going to grow that big because only a handful of context keys will ever be created and each one only takes up a small amount of space.

Context keys are also meant to be stored as constants, because each one is unique and must be available to allow querying - so even if we did have some kind of weak map here it wouldn't be evicting much because it would be rare for the mapped key to become unreachable during the application lifetime.

Copy link
Collaborator

Choose a reason for hiding this comment

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

ok thank you for having given more "context for this context" :)

new ConcurrentHashMap<>();

private final Span currentSpan;
private final Span rootSpan;
private final datadog.context.Context delegate;

private final Object[] entries;
public OtelContext(datadog.context.Context delegate) {
this.delegate = delegate;
}

public OtelContext(Span currentSpan, Span rootSpan) {
this(currentSpan, rootSpan, NO_ENTRIES);
public static Context current() {
return new OtelContext(datadog.context.Context.current());
}

public OtelContext(Span currentSpan, Span rootSpan, Object[] entries) {
this.currentSpan = currentSpan;
this.rootSpan = rootSpan;
this.entries = entries;
@Override
public Scope makeCurrent() {
return new OtelScope(Context.super.makeCurrent(), delegate.attach());
}

@Nullable
@Override
@SuppressWarnings("unchecked")
public <V> V get(ContextKey<V> key) {
if (OTEL_CONTEXT_SPAN_KEY.equals(key.toString())) {
return (V) this.currentSpan;
AgentSpan span = AgentSpan.fromContext(delegate);
if (span != null) {
return (V) toOtelSpan(span);
}
// fall-through and check for non-datadog span data
} else if (OTEL_CONTEXT_ROOT_SPAN_KEY.equals(key.toString())) {
return (V) this.rootSpan;
}
for (int i = 0; i < this.entries.length; i += 2) {
if (this.entries[i] == key) {
return (V) this.entries[i + 1];
AgentSpan span = AgentSpan.fromContext(delegate);
if (span != null) {
return (V) toOtelSpan(span.getLocalRootSpan());
}
// fall-through and check for non-datadog span data
}
return null;
return (V) delegate.get(delegateKey(key));
}

@Override
public <V> Context with(ContextKey<V> key, V value) {
if (OTEL_CONTEXT_SPAN_KEY.equals(key.toString())) {
return new OtelContext((Span) value, this.rootSpan, this.entries);
} else if (OTEL_CONTEXT_ROOT_SPAN_KEY.equals(key.toString())) {
return new OtelContext(this.currentSpan, (Span) value, this.entries);
}
Object[] newEntries = null;
int oldEntriesLength = this.entries.length;
for (int i = 0; i < oldEntriesLength; i += 2) {
if (this.entries[i] == key) {
if (this.entries[i + 1] == value) {
return this;
}
newEntries = this.entries.clone();
newEntries[i + 1] = value;
break;
if (value instanceof OtelSpan) {
AgentSpan span = ((OtelSpan) value).asAgentSpan();
return new OtelContext(delegate.with(span));
}
// fall-through and store as non-datadog span data
}
if (null == newEntries) {
newEntries = Arrays.copyOf(this.entries, oldEntriesLength + 2);
newEntries[oldEntriesLength] = key;
newEntries[oldEntriesLength + 1] = value;
}
return new OtelContext(this.currentSpan, this.rootSpan, newEntries);
return new OtelContext(delegate.with(delegateKey(key), value));
}

@Override
public Scope makeCurrent() {
final Scope scope = Context.super.makeCurrent();
if (this.currentSpan instanceof OtelSpan) {
// only keep propagated context until next span activation
lastPropagated.remove();
AgentScope agentScope = ((OtelSpan) this.currentSpan).activate();
return new OtelScope(scope, agentScope, this.entries);
} else {
// propagated context not on the scope stack, capture it here
lastPropagated.set(this);
return new Scope() {
@Override
public void close() {
lastPropagated.remove();
scope.close();
}
};
}
}

public static Context current() {
// Check for propagated context not on the scope stack
Context context = lastPropagated.get();
if (null != context) {
return context;
}
// Check empty context
AgentScope agentCurrentScope = AgentTracer.activeScope();
if (null == agentCurrentScope) {
return OtelContext.ROOT;
}
// Get OTel current span
Span otelCurrentSpan = null;
AgentSpan agentCurrentSpan = agentCurrentScope.span();
if (agentCurrentSpan instanceof AttachableWrapper) {
Object wrapper = ((AttachableWrapper) agentCurrentSpan).getWrapper();
if (wrapper instanceof OtelSpan) {
otelCurrentSpan = (OtelSpan) wrapper;
}
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
if (otelCurrentSpan == null) {
otelCurrentSpan = new OtelSpan(agentCurrentSpan);
}
// Get OTel root span
Span otelRootSpan = null;
AgentSpan agentRootSpan = agentCurrentSpan.getLocalRootSpan();
if (agentRootSpan instanceof AttachableWrapper) {
Object wrapper = ((AttachableWrapper) agentRootSpan).getWrapper();
if (wrapper instanceof OtelSpan) {
otelRootSpan = (OtelSpan) wrapper;
}
}
if (otelRootSpan == null) {
otelRootSpan = new OtelSpan(agentRootSpan);
}
// Get OTel custom context entries
Object[] contextEntries = NO_ENTRIES;
if (agentCurrentScope instanceof AttachableWrapper) {
Object wrapper = ((AttachableWrapper) agentCurrentScope).getWrapper();
if (wrapper instanceof OtelScope) {
contextEntries = ((OtelScope) wrapper).contextEntries();
}
}
return new OtelContext(otelCurrentSpan, otelRootSpan, contextEntries);
return delegate.equals(((OtelContext) o).delegate);
}

/** Last propagated context not on the scope stack; {@code null} if there's no such context. */
@Nullable
public static Context lastPropagated() {
return lastPropagated.get();
@Override
public int hashCode() {
return delegate.hashCode();
}

@Override
public String toString() {
return "OtelContext{"
+ "currentSpan="
+ this.currentSpan.getSpanContext()
+ ", rootSpan="
+ this.rootSpan.getSpanContext()
+ '}';
return "OtelContext{" + "delegate=" + delegate + '}';
}

private static datadog.context.ContextKey delegateKey(ContextKey key) {
return DELEGATE_KEYS.computeIfAbsent(key, OtelContext::mapByKeyName);
}

private static datadog.context.ContextKey mapByKeyName(ContextKey key) {
return datadog.context.ContextKey.named(key.toString());
}

private static OtelSpan toOtelSpan(AgentSpan span) {
if (span instanceof AttachableWrapper) {
Object wrapper = ((AttachableWrapper) span).getWrapper();
if (wrapper instanceof OtelSpan) {
return (OtelSpan) wrapper;
}
}
return new OtelSpan(span);
}
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,20 @@
package datadog.opentelemetry.shim.context;

import datadog.trace.bootstrap.instrumentation.api.AgentScope;
import datadog.trace.bootstrap.instrumentation.api.AttachableWrapper;
import datadog.context.ContextScope;
import io.opentelemetry.context.Scope;

public class OtelScope implements Scope {
private final Scope scope;
private final AgentScope delegate;
private final Object[] contextEntries;
private final ContextScope delegate;

public OtelScope(Scope scope, AgentScope delegate, Object[] contextEntries) {
public OtelScope(Scope scope, ContextScope delegate) {
this.scope = scope;
this.delegate = delegate;
this.contextEntries = contextEntries;
if (delegate instanceof AttachableWrapper) {
((AttachableWrapper) delegate).attachWrapper(this);
}
}

/** Context entries from {@link OtelContext}, captured when the context was made current. */
Object[] contextEntries() {
return contextEntries;
}

@Override
public void close() {
this.delegate.close();
this.scope.close();
delegate.close();
scope.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import datadog.opentelemetry.shim.context.OtelContext;
import datadog.opentelemetry.shim.trace.OtelExtractedContext;
import datadog.opentelemetry.shim.trace.OtelSpan;
import datadog.trace.api.TracePropagationStyle;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext;
Expand Down Expand Up @@ -59,7 +58,7 @@ public <C> Context extract(Context context, @Nullable C carrier, TextMapGetter<C
} else {
TraceState traceState = extractTraceState(extracted, carrier, getter);
SpanContext spanContext = fromRemote(extracted, traceState);
return new OtelContext(Span.wrap(spanContext), OtelSpan.invalid());
return Span.wrap(spanContext).storeInContext(OtelContext.ROOT);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import static java.lang.Boolean.parseBoolean;
import static java.util.Locale.ROOT;

import datadog.opentelemetry.shim.context.OtelContext;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext;
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
Expand Down Expand Up @@ -202,11 +201,7 @@ public Span startSpan() {
setSpanKind(INTERNAL);
}
if (!this.ignoreActiveSpan) {
// support automatic parenting from propagated context not on the scope stack
Context context = OtelContext.lastPropagated();
if (null != context) {
setParent(context);
}
setParent(Context.current());
}
AgentSpan delegate = this.delegate.start();
// Apply overrides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ public boolean onlyMatchKnownTypes() {
public String[] helperClassNames() {
return new String[] {
"datadog.opentelemetry.shim.context.OtelContext",
"datadog.opentelemetry.shim.context.OtelContext$1",
"datadog.opentelemetry.shim.context.OtelScope",
"datadog.opentelemetry.shim.context.propagation.AgentTextMapPropagator",
"datadog.opentelemetry.shim.context.propagation.OtelContextPropagators",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ public boolean onlyMatchKnownTypes() {
public String[] helperClassNames() {
return new String[] {
"datadog.opentelemetry.shim.context.OtelContext",
"datadog.opentelemetry.shim.context.OtelContext$1",
"datadog.opentelemetry.shim.context.OtelScope",
"datadog.opentelemetry.shim.trace.OtelExtractedContext",
"datadog.opentelemetry.shim.trace.OtelConventions",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ public boolean onlyMatchKnownTypes() {
public String[] helperClassNames() {
return new String[] {
"datadog.opentelemetry.shim.context.OtelContext",
"datadog.opentelemetry.shim.context.OtelContext$1",
"datadog.opentelemetry.shim.context.OtelScope",
"datadog.opentelemetry.shim.trace.OtelExtractedContext",
"datadog.opentelemetry.shim.trace.OtelConventions",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import static datadog.opentelemetry.shim.context.OtelContext.OTEL_CONTEXT_ROOT_S
import static datadog.opentelemetry.shim.context.OtelContext.OTEL_CONTEXT_SPAN_KEY
import static datadog.opentelemetry.shim.trace.OtelConventions.SPAN_KIND_INTERNAL

class ContextTest extends AgentTestRunner {
class ContextForkedTest extends AgentTestRunner {
@Subject
def tracer = GlobalOpenTelemetry.get().tracerProvider.get("context-instrumentation")

Expand Down Expand Up @@ -169,6 +169,10 @@ class ContextTest extends AgentTestRunner {
then:
currentSpan != null
!currentSpan.spanContext.isValid()

cleanup:
ddScope.close()
ddSpan.finish()
}

def "test clearing context"() {
Expand Down Expand Up @@ -238,6 +242,14 @@ class ContextTest extends AgentTestRunner {
}
}
}

cleanup:
otelGrandChildScope?.close()
otelGrandChildSpan?.end()
ddChildScope?.close()
ddChildSpan?.finish()
otelParentScope.close()
otelParentSpan.end()
}

def "test context spans retrieval"() {
Expand Down
Loading