Skip to content

Commit 3900e56

Browse files
authored
Allow use of span as a primordial context (#8250)
* Support Context.with(span) for convenience * Allow AgentSpan to act as a primordial Context * Allow context implementation to skip interim copy when adding two entries at the same time (such as adding span plus baggage)
1 parent 1f71dbd commit 3900e56

File tree

9 files changed

+98
-2
lines changed

9 files changed

+98
-2
lines changed

components/context/src/main/java/datadog/context/Context.java

+23-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ static Context detachFrom(Object carrier) {
114114
/**
115115
* Creates a copy of this context with the given key-value set.
116116
*
117-
* <p>Existing value with the given key will be replaced, and mapping to a {@code null} value will
117+
* <p>Existing value with the given key will be replaced. Mapping to a {@code null} value will
118118
* remove the key-value from the context copy.
119119
*
120120
* @param <T> the type of the value.
@@ -124,6 +124,28 @@ static Context detachFrom(Object carrier) {
124124
*/
125125
<T> Context with(ContextKey<T> key, @Nullable T value);
126126

127+
/**
128+
* Creates a copy of this context with the given pair of key-values.
129+
*
130+
* <p>Existing values with the given keys will be replaced. Mapping to a {@code null} value will
131+
* remove the key-value from the context copy.
132+
*
133+
* @param <T> the type of the first value.
134+
* @param <U> the type of the second value.
135+
* @param firstKey the first key to store the first value.
136+
* @param firstValue the first value to store.
137+
* @param secondKey the second key to store the second value.
138+
* @param secondValue the second value to store.
139+
* @return a new context with the pair of key-values set.
140+
*/
141+
default <T, U> Context with(
142+
ContextKey<T> firstKey,
143+
@Nullable T firstValue,
144+
ContextKey<U> secondKey,
145+
@Nullable U secondValue) {
146+
return with(firstKey, firstValue).with(secondKey, secondValue);
147+
}
148+
127149
/**
128150
* Creates a copy of this context with the implicit key is mapped to the value.
129151
*

components/context/src/test/java/datadog/context/ContextTest.java

+35
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,41 @@ void testWith(Context context) {
6363
assertDoesNotThrow(() -> context.with(null), "Null implicitly keyed value not throw exception");
6464
}
6565

66+
@ParameterizedTest
67+
@MethodSource("contextImplementations")
68+
void testWithPair(Context context) {
69+
// Test retrieving value
70+
String stringValue = "value";
71+
Context context1 = context.with(BOOLEAN_KEY, false, STRING_KEY, stringValue);
72+
assertEquals(stringValue, context1.get(STRING_KEY));
73+
assertEquals(false, context1.get(BOOLEAN_KEY));
74+
// Test overriding value
75+
String stringValue2 = "value2";
76+
Context context2 = context1.with(STRING_KEY, stringValue2, BOOLEAN_KEY, true);
77+
assertEquals(stringValue2, context2.get(STRING_KEY));
78+
assertEquals(true, context2.get(BOOLEAN_KEY));
79+
// Test clearing value
80+
Context context3 = context2.with(BOOLEAN_KEY, null, STRING_KEY, null);
81+
assertNull(context3.get(STRING_KEY));
82+
assertNull(context3.get(BOOLEAN_KEY));
83+
// Test null key handling
84+
assertThrows(
85+
NullPointerException.class,
86+
() -> context.with(null, "test", STRING_KEY, "test"),
87+
"Context forbids null keys");
88+
assertThrows(
89+
NullPointerException.class,
90+
() -> context.with(STRING_KEY, "test", null, "test"),
91+
"Context forbids null keys");
92+
// Test null value handling
93+
assertDoesNotThrow(
94+
() -> context.with(BOOLEAN_KEY, null, STRING_KEY, "test"),
95+
"Null value should not throw exception");
96+
assertDoesNotThrow(
97+
() -> context.with(STRING_KEY, "test", BOOLEAN_KEY, null),
98+
"Null value should not throw exception");
99+
}
100+
66101
@ParameterizedTest
67102
@MethodSource("contextImplementations")
68103
void testGet(Context original) {

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Constants.java

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public final class Constants {
1515
*/
1616
public static final String[] BOOTSTRAP_PACKAGE_PREFIXES = {
1717
"datadog.slf4j",
18+
"datadog.context",
1819
"datadog.appsec.api",
1920
"datadog.trace.api",
2021
"datadog.trace.bootstrap",

dd-java-agent/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ def sharedShadowJar = tasks.register('sharedShadowJar', ShadowJar) {
135135
exclude(project(':dd-java-agent:agent-logging'))
136136
exclude(project(':dd-trace-api'))
137137
exclude(project(':internal-api'))
138+
exclude(project(':components:context'))
138139
exclude(project(':utils:time-utils'))
139140
exclude(dependency('org.slf4j::'))
140141
}

dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/SpockRunner.java

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public class SpockRunner extends JUnitPlatform {
3838
*/
3939
public static final String[] BOOTSTRAP_PACKAGE_PREFIXES_COPY = {
4040
"datadog.slf4j",
41+
"datadog.context",
4142
"datadog.appsec.api",
4243
"datadog.trace.api",
4344
"datadog.trace.bootstrap",

gradle/dependencies.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ final class CachedData {
1616
exclude(project(':internal-api'))
1717
exclude(project(':internal-api:internal-api-9'))
1818
exclude(project(':communication'))
19+
exclude(project(':components:context'))
1920
exclude(project(':components:json'))
2021
exclude(project(':remote-config:remote-config-api'))
2122
exclude(project(':remote-config:remote-config-core'))

internal-api/build.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ excludedClassesCoverage += [
8181
"datadog.trace.bootstrap.instrumentation.api.StatsPoint",
8282
"datadog.trace.bootstrap.instrumentation.api.Schema",
8383
"datadog.trace.bootstrap.instrumentation.api.ScopeSource",
84+
"datadog.trace.bootstrap.instrumentation.api.InternalContextKeys",
8485
"datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes",
8586
"datadog.trace.bootstrap.instrumentation.api.TagContext",
8687
"datadog.trace.bootstrap.instrumentation.api.TagContext.HttpHeaders",
@@ -210,6 +211,7 @@ dependencies {
210211
// references TraceScope and Continuation from public api
211212
api project(':dd-trace-api')
212213
api libs.slf4j
214+
api project(':components:context')
213215
api project(":utils:time-utils")
214216

215217
// has to be loaded by system classloader:

internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
package datadog.trace.bootstrap.instrumentation.api;
22

3+
import static datadog.trace.bootstrap.instrumentation.api.InternalContextKeys.SPAN_KEY;
4+
5+
import datadog.context.Context;
6+
import datadog.context.ContextKey;
7+
import datadog.context.ImplicitContextKeyed;
38
import datadog.trace.api.DDTraceId;
49
import datadog.trace.api.TraceConfig;
510
import datadog.trace.api.gateway.IGSpanInfo;
611
import datadog.trace.api.gateway.RequestContext;
712
import datadog.trace.api.interceptor.MutableSpan;
13+
import javax.annotation.Nullable;
814

9-
public interface AgentSpan extends MutableSpan, IGSpanInfo, WithAgentSpan {
15+
public interface AgentSpan
16+
extends MutableSpan, ImplicitContextKeyed, Context, IGSpanInfo, WithAgentSpan {
1017

1118
DDTraceId getTraceId();
1219

@@ -145,4 +152,21 @@ public interface AgentSpan extends MutableSpan, IGSpanInfo, WithAgentSpan {
145152
default AgentSpan asAgentSpan() {
146153
return this;
147154
}
155+
156+
@Override
157+
default Context storeInto(Context context) {
158+
return context.with(SPAN_KEY, this);
159+
}
160+
161+
@Nullable
162+
@Override
163+
default <T> T get(ContextKey<T> key) {
164+
// noinspection unchecked
165+
return SPAN_KEY == key ? (T) this : Context.root().get(key);
166+
}
167+
168+
@Override
169+
default <T> Context with(ContextKey<T> key, @Nullable T value) {
170+
return Context.root().with(SPAN_KEY, this, key, value);
171+
}
148172
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package datadog.trace.bootstrap.instrumentation.api;
2+
3+
import datadog.context.ContextKey;
4+
5+
final class InternalContextKeys {
6+
static final ContextKey<AgentSpan> SPAN_KEY = ContextKey.named("dd-span-key");
7+
8+
private InternalContextKeys() {}
9+
}

0 commit comments

Comments
 (0)