Skip to content

Commit f036673

Browse files
laurittrask
andauthored
Allow reading otel context from reactor ContextView (#11235)
Co-authored-by: Trask Stalnaker <[email protected]>
1 parent 9727c6e commit f036673

File tree

11 files changed

+241
-16
lines changed

11 files changed

+241
-16
lines changed

instrumentation/reactor/reactor-3.1/javaagent/build.gradle.kts

+10-5
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@ muzzle {
1414
}
1515

1616
tasks.withType<Test>().configureEach {
17+
systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
1718
// TODO run tests both with and without experimental span attributes
1819
jvmArgs("-Dotel.instrumentation.reactor.experimental-span-attributes=true")
1920
}
2021

2122
dependencies {
23+
// we compile against 3.4.0, so we could use reactor.util.context.ContextView
24+
// instrumentation is tested against 3.1.0.RELEASE
25+
compileOnly("io.projectreactor:reactor-core:3.4.0")
2226
implementation(project(":instrumentation:reactor:reactor-3.1:library"))
23-
library("io.projectreactor:reactor-core:3.1.0.RELEASE")
2427

2528
implementation(project(":instrumentation:opentelemetry-api:opentelemetry-api-1.0:javaagent"))
2629

@@ -30,14 +33,12 @@ dependencies {
3033

3134
testInstrumentation(project(":instrumentation:opentelemetry-extension-annotations-1.0:javaagent"))
3235

36+
testLibrary("io.projectreactor:reactor-core:3.1.0.RELEASE")
3337
testLibrary("io.projectreactor:reactor-test:3.1.0.RELEASE")
3438
testImplementation(project(":instrumentation-annotations-support-testing"))
3539
testImplementation(project(":instrumentation:reactor:reactor-3.1:testing"))
3640
testImplementation(project(":instrumentation-annotations"))
3741
testImplementation("io.opentelemetry:opentelemetry-extension-annotations")
38-
39-
latestDepTestLibrary("io.projectreactor:reactor-core:3.4.+")
40-
latestDepTestLibrary("io.projectreactor:reactor-test:3.4.+")
4142
}
4243

4344
testing {
@@ -46,7 +47,11 @@ testing {
4647
dependencies {
4748
implementation(project(":instrumentation:reactor:reactor-3.1:library"))
4849
implementation(project(":instrumentation-annotations"))
49-
implementation("io.projectreactor:reactor-test:3.1.0.RELEASE")
50+
if (findProperty("testLatestDeps") as Boolean) {
51+
implementation("io.projectreactor:reactor-test:+")
52+
} else {
53+
implementation("io.projectreactor:reactor-test:3.1.0.RELEASE")
54+
}
5055
}
5156
}
5257
}

instrumentation/reactor/reactor-3.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/reactor/v3_1/BaseMonoWithSpanTest.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ void nested() {
8888
span ->
8989
span.hasName("inner-manual")
9090
.hasKind(SpanKind.INTERNAL)
91-
.hasParent(trace.getSpan(1))
91+
// earliest tested and latest version behave differently
92+
.hasParent(trace.getSpan(Boolean.getBoolean("testLatestDeps") ? 0 : 1))
9293
.hasAttributes(Attributes.empty())));
9394
}
9495

@@ -130,7 +131,7 @@ void nestedFromCurrent() {
130131
span ->
131132
span.hasName("inner-manual")
132133
.hasKind(SpanKind.INTERNAL)
133-
.hasParent(trace.getSpan(1))
134+
.hasParent(trace.getSpan(Boolean.getBoolean("testLatestDeps") ? 0 : 1))
134135
.hasAttributes(Attributes.empty())));
135136
}
136137

instrumentation/reactor/reactor-3.1/library/build.gradle.kts

+10-3
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,19 @@ plugins {
33
}
44

55
dependencies {
6-
library("io.projectreactor:reactor-core:3.1.0.RELEASE")
6+
// we compile against 3.4.0, so we could use reactor.util.context.ContextView
7+
// instrumentation is expected it to work with 3.1.0.RELEASE
8+
compileOnly("io.projectreactor:reactor-core:3.4.0")
9+
compileOnly(project(":muzzle")) // For @NoMuzzle
710
implementation(project(":instrumentation-annotations-support"))
11+
testLibrary("io.projectreactor:reactor-core:3.1.0.RELEASE")
812
testLibrary("io.projectreactor:reactor-test:3.1.0.RELEASE")
913

1014
testImplementation(project(":instrumentation:reactor:reactor-3.1:testing"))
15+
}
1116

12-
latestDepTestLibrary("io.projectreactor:reactor-core:3.4.+")
13-
latestDepTestLibrary("io.projectreactor:reactor-test:3.4.+")
17+
tasks {
18+
withType<Test>().configureEach {
19+
systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
20+
}
1421
}

instrumentation/reactor/reactor-3.1/library/src/main/java/io/opentelemetry/instrumentation/reactor/v3_1/ContextPropagationOperator.java

+15
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import io.opentelemetry.context.Context;
2828
import io.opentelemetry.context.Scope;
2929
import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndStrategies;
30+
import io.opentelemetry.javaagent.tooling.muzzle.NoMuzzle;
3031
import java.lang.invoke.MethodHandle;
3132
import java.lang.invoke.MethodHandles;
3233
import java.util.function.BiFunction;
@@ -134,6 +135,20 @@ public static Context getOpenTelemetryContext(
134135
return context.getOrDefault(TRACE_CONTEXT_KEY, defaultTraceContext);
135136
}
136137

138+
/**
139+
* Gets Trace {@link Context} from Reactor {@link reactor.util.context.ContextView}.
140+
*
141+
* @param contextView Reactor's context to get trace context from.
142+
* @param defaultTraceContext Default value to be returned if no trace context is found on Reactor
143+
* context.
144+
* @return Trace context or default value.
145+
*/
146+
@NoMuzzle
147+
public static Context getOpenTelemetryContextFromContextView(
148+
reactor.util.context.ContextView contextView, Context defaultTraceContext) {
149+
return contextView.getOrDefault(TRACE_CONTEXT_KEY, defaultTraceContext);
150+
}
151+
137152
ContextPropagationOperator(boolean captureExperimentalSpanAttributes) {
138153
this.asyncOperationEndStrategy =
139154
ReactorAsyncOperationEndStrategy.builder()

instrumentation/reactor/reactor-3.1/library/src/test/java/io/opentelemetry/instrumentation/reactor/v3_1/HooksTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ void testInvalidBlockUsage() throws InterruptedException {
6262
Disposable disposable =
6363
Mono.defer(
6464
() ->
65-
Mono.fromCallable(callable).publishOn(Schedulers.elastic()).flatMap(Mono::just))
65+
Mono.fromCallable(callable).publishOn(Schedulers.single()).flatMap(Mono::just))
6666
.subscribeOn(Schedulers.single())
6767
.subscribe();
6868

instrumentation/reactor/reactor-3.1/library/src/test/java/io/opentelemetry/instrumentation/reactor/v3_1/ReactorCoreTest.java

+12-5
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,18 @@ void fluxInNonBlockingPublisherAssembly() {
160160

161161
@Test
162162
void nestedNonBlocking() {
163+
boolean testLatestDeps = Boolean.getBoolean("testLatestDeps");
163164
int result =
164165
testing.runWithSpan(
165166
"parent",
166167
() ->
167168
Mono.defer(
168169
() -> {
169-
Span.current().setAttribute("middle", "foo");
170+
// earliest tested and latest version behave differently
171+
// in latest dep test current span is "parent" not "middle"
172+
if (!testLatestDeps) {
173+
Span.current().setAttribute("middle", "foo");
174+
}
170175
return Mono.fromCallable(
171176
() -> {
172177
Span.current().setAttribute("inner", "bar");
@@ -183,10 +188,12 @@ void nestedNonBlocking() {
183188
trace ->
184189
trace.hasSpansSatisfyingExactly(
185190
span -> span.hasName("parent").hasNoParent(),
186-
span ->
187-
span.hasName("middle")
188-
.hasParent(trace.getSpan(0))
189-
.hasAttributes(attributeEntry("middle", "foo")),
191+
span -> {
192+
span.hasName("middle").hasParent(trace.getSpan(0));
193+
if (!testLatestDeps) {
194+
span.hasAttributes(attributeEntry("middle", "foo"));
195+
}
196+
},
190197
span ->
191198
span.hasName("inner")
192199
.hasParent(trace.getSpan(1))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
group.set("io.projectreactor")
8+
module.set("reactor-core")
9+
versions.set("[3.4.0,)")
10+
extraDependency("io.opentelemetry:opentelemetry-api:1.0.0")
11+
assertInverse.set(true)
12+
excludeInstrumentationName("opentelemetry-api")
13+
}
14+
}
15+
16+
dependencies {
17+
library("io.projectreactor:reactor-core:3.4.0")
18+
implementation(project(":instrumentation:reactor:reactor-3.1:library"))
19+
20+
implementation(project(":instrumentation:opentelemetry-api:opentelemetry-api-1.0:javaagent"))
21+
22+
compileOnly(project(":javaagent-tooling"))
23+
compileOnly(project(":instrumentation-annotations-support"))
24+
compileOnly(project(":opentelemetry-api-shaded-for-instrumenting", configuration = "shadow"))
25+
26+
testInstrumentation(project(":instrumentation:reactor:reactor-3.1:javaagent"))
27+
testInstrumentation(project(":instrumentation:opentelemetry-extension-annotations-1.0:javaagent"))
28+
29+
testLibrary("io.projectreactor:reactor-test:3.1.0.RELEASE")
30+
testImplementation(project(":instrumentation-annotations-support-testing"))
31+
testImplementation(project(":instrumentation:reactor:reactor-3.1:testing"))
32+
testImplementation(project(":instrumentation-annotations"))
33+
testImplementation("io.opentelemetry:opentelemetry-extension-annotations")
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.reactor.v3_4.operator;
7+
8+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
9+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
10+
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
11+
import static net.bytebuddy.matcher.ElementMatchers.named;
12+
import static net.bytebuddy.matcher.ElementMatchers.returns;
13+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
14+
15+
import application.io.opentelemetry.context.Context;
16+
import io.opentelemetry.instrumentation.reactor.v3_1.ContextPropagationOperator;
17+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
18+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
19+
import io.opentelemetry.javaagent.instrumentation.opentelemetryapi.context.AgentContextStorage;
20+
import net.bytebuddy.asm.Advice;
21+
import net.bytebuddy.description.type.TypeDescription;
22+
import net.bytebuddy.matcher.ElementMatcher;
23+
24+
public class ContextPropagationOperator34Instrumentation implements TypeInstrumentation {
25+
@Override
26+
public ElementMatcher<TypeDescription> typeMatcher() {
27+
return named(
28+
"application.io.opentelemetry.instrumentation.reactor.v3_1.ContextPropagationOperator");
29+
}
30+
31+
@Override
32+
public void transform(TypeTransformer transformer) {
33+
transformer.applyAdviceToMethod(
34+
isMethod()
35+
.and(isPublic())
36+
.and(isStatic())
37+
.and(named("getOpenTelemetryContextFromContextView"))
38+
.and(takesArgument(0, named("reactor.util.context.ContextView")))
39+
.and(takesArgument(1, named("application.io.opentelemetry.context.Context")))
40+
.and(returns(named("application.io.opentelemetry.context.Context"))),
41+
ContextPropagationOperator34Instrumentation.class.getName() + "$GetContextViewAdvice");
42+
}
43+
44+
@SuppressWarnings("unused")
45+
public static class GetContextViewAdvice {
46+
@Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
47+
public static boolean methodEnter() {
48+
return false;
49+
}
50+
51+
@Advice.OnMethodExit(suppress = Throwable.class)
52+
public static void methodExit(
53+
@Advice.Argument(0) reactor.util.context.ContextView reactorContext,
54+
@Advice.Argument(1) Context defaultContext,
55+
@Advice.Return(readOnly = false) Context applicationContext) {
56+
57+
io.opentelemetry.context.Context agentContext =
58+
ContextPropagationOperator.getOpenTelemetryContextFromContextView(reactorContext, null);
59+
if (agentContext == null) {
60+
applicationContext = defaultContext;
61+
} else {
62+
applicationContext = AgentContextStorage.toApplicationContext(agentContext);
63+
}
64+
}
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.reactor.v3_4.operator;
7+
8+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
9+
import static java.util.Collections.singletonList;
10+
11+
import com.google.auto.service.AutoService;
12+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
13+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
14+
import java.util.List;
15+
import net.bytebuddy.matcher.ElementMatcher;
16+
17+
@AutoService(InstrumentationModule.class)
18+
public class ContextPropagationOperator34InstrumentationModule extends InstrumentationModule {
19+
20+
public ContextPropagationOperator34InstrumentationModule() {
21+
super("reactor", "reactor-3.4", "reactor-context-propagation-operator");
22+
}
23+
24+
@Override
25+
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
26+
return hasClassesNamed(
27+
"application.io.opentelemetry.context.Context", "reactor.util.context.ContextView");
28+
}
29+
30+
@Override
31+
public List<TypeInstrumentation> typeInstrumentations() {
32+
return singletonList(new ContextPropagationOperator34Instrumentation());
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.reactor.v3_4;
7+
8+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.attributeEntry;
9+
import static org.assertj.core.api.Assertions.assertThat;
10+
11+
import io.opentelemetry.api.trace.Span;
12+
import io.opentelemetry.api.trace.SpanKind;
13+
import io.opentelemetry.context.Context;
14+
import io.opentelemetry.instrumentation.reactor.v3_1.ContextPropagationOperator;
15+
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
16+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
17+
import org.junit.jupiter.api.Test;
18+
import org.junit.jupiter.api.extension.RegisterExtension;
19+
20+
class ContextPropagationOperator34InstrumentationTest {
21+
22+
@RegisterExtension
23+
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
24+
25+
@Test
26+
void storeAndGetContext() {
27+
reactor.util.context.Context reactorContext = reactor.util.context.Context.empty();
28+
testing.runWithSpan(
29+
"parent",
30+
() -> {
31+
reactor.util.context.Context newReactorContext =
32+
ContextPropagationOperator.storeOpenTelemetryContext(
33+
reactorContext, Context.current());
34+
Context otelContext =
35+
ContextPropagationOperator.getOpenTelemetryContextFromContextView(
36+
newReactorContext, null);
37+
assertThat(otelContext).isNotNull();
38+
Span.fromContext(otelContext).setAttribute("foo", "bar");
39+
Context otelContext2 =
40+
ContextPropagationOperator.getOpenTelemetryContext(newReactorContext, null);
41+
assertThat(otelContext2).isNotNull();
42+
Span.fromContext(otelContext2).setAttribute("foo2", "bar2");
43+
});
44+
45+
testing.waitAndAssertTraces(
46+
trace ->
47+
trace.hasSpansSatisfyingExactly(
48+
span ->
49+
span.hasName("parent")
50+
.hasKind(SpanKind.INTERNAL)
51+
.hasNoParent()
52+
.hasAttributes(
53+
attributeEntry("foo", "bar"), attributeEntry("foo2", "bar2"))));
54+
}
55+
}

settings.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@ include(":instrumentation:ratpack:ratpack-1.7:library")
465465
include(":instrumentation:reactor:reactor-3.1:javaagent")
466466
include(":instrumentation:reactor:reactor-3.1:library")
467467
include(":instrumentation:reactor:reactor-3.1:testing")
468+
include(":instrumentation:reactor:reactor-3.4:javaagent")
468469
include(":instrumentation:reactor:reactor-kafka-1.0:javaagent")
469470
include(":instrumentation:reactor:reactor-kafka-1.0:testing")
470471
include(":instrumentation:reactor:reactor-netty:reactor-netty-0.9:javaagent")

0 commit comments

Comments
 (0)