Skip to content

Commit 56dfd1e

Browse files
laurittrask
andauthored
Implement @WithSpan support for kotlin coroutines (#8870)
Co-authored-by: Trask Stalnaker <[email protected]>
1 parent dae1725 commit 56dfd1e

File tree

17 files changed

+1229
-5
lines changed

17 files changed

+1229
-5
lines changed

dependencyManagement/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ val CORE_DEPENDENCIES = listOf(
6565
"net.bytebuddy:byte-buddy-gradle-plugin:${byteBuddyVersion}",
6666
"org.ow2.asm:asm:${asmVersion}",
6767
"org.ow2.asm:asm-tree:${asmVersion}",
68+
"org.ow2.asm:asm-util:${asmVersion}",
6869
"org.openjdk.jmh:jmh-core:${jmhVersion}",
6970
"org.openjdk.jmh:jmh-generator-bytecode:${jmhVersion}",
7071
"org.mockito:mockito-core:${mockitoVersion}",

instrumentation/kotlinx-coroutines/javaagent/build.gradle.kts

+12
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,35 @@ muzzle {
1010
group.set("org.jetbrains.kotlinx")
1111
module.set("kotlinx-coroutines-core")
1212
versions.set("[1.0.0,1.3.8)")
13+
extraDependency(project(":instrumentation-annotations"))
14+
extraDependency("io.opentelemetry:opentelemetry-api:1.27.0")
1315
}
1416
// 1.3.9 (and beyond?) have changed how artifact names are resolved due to multiplatform variants
1517
pass {
1618
group.set("org.jetbrains.kotlinx")
1719
module.set("kotlinx-coroutines-core-jvm")
1820
versions.set("[1.3.9,)")
21+
extraDependency(project(":instrumentation-annotations"))
22+
extraDependency("io.opentelemetry:opentelemetry-api:1.27.0")
1923
}
2024
}
2125

2226
dependencies {
2327
compileOnly("io.opentelemetry:opentelemetry-extension-kotlin")
2428
compileOnly("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
29+
compileOnly(project(":opentelemetry-instrumentation-annotations-shaded-for-instrumenting", configuration = "shadow"))
30+
31+
implementation("org.ow2.asm:asm-tree")
32+
implementation("org.ow2.asm:asm-util")
33+
implementation(project(":instrumentation:opentelemetry-instrumentation-annotations-1.16:javaagent"))
2534

2635
testInstrumentation(project(":instrumentation:opentelemetry-extension-kotlin-1.0:javaagent"))
2736
testInstrumentation(project(":instrumentation:reactor:reactor-3.1:javaagent"))
2837

2938
testImplementation("io.opentelemetry:opentelemetry-extension-kotlin")
3039
testImplementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
3140
testImplementation(project(":instrumentation:reactor:reactor-3.1:library"))
41+
testImplementation(project(":instrumentation-annotations"))
3242

3343
// Use first version with flow support since we have tests for it.
3444
testLibrary("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0")
@@ -39,6 +49,8 @@ tasks {
3949
withType(KotlinCompile::class).configureEach {
4050
kotlinOptions {
4151
jvmTarget = "1.8"
52+
// generate metadata for Java 1.8 reflection on method parameters, used in @WithSpan tests
53+
javaParameters = true
4254
}
4355
}
4456
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.instrumentationannotations;
7+
8+
import static io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.instrumentationannotations.AnnotationSingletons.instrumenter;
9+
10+
import io.opentelemetry.api.trace.Span;
11+
import io.opentelemetry.api.trace.SpanKind;
12+
import io.opentelemetry.context.Context;
13+
import io.opentelemetry.context.Scope;
14+
import io.opentelemetry.instrumentation.api.util.VirtualField;
15+
import kotlin.coroutines.Continuation;
16+
import kotlin.coroutines.intrinsics.IntrinsicsKt;
17+
18+
public final class AnnotationInstrumentationHelper {
19+
20+
private static final VirtualField<Continuation<?>, Context> contextField =
21+
VirtualField.find(Continuation.class, Context.class);
22+
23+
public static MethodRequest createMethodRequest(
24+
Class<?> declaringClass, String methodName, String withSpanValue, String spanKindString) {
25+
SpanKind spanKind = SpanKind.INTERNAL;
26+
if (spanKindString != null) {
27+
try {
28+
spanKind = SpanKind.valueOf(spanKindString);
29+
} catch (IllegalArgumentException exception) {
30+
// ignore
31+
}
32+
}
33+
34+
return MethodRequest.create(declaringClass, methodName, withSpanValue, spanKind);
35+
}
36+
37+
public static Context enterCoroutine(
38+
int label, Continuation<?> continuation, MethodRequest request) {
39+
// label 0 means that coroutine is started, any other label means that coroutine is resumed
40+
if (label == 0) {
41+
Context context = instrumenter().start(Context.current(), request);
42+
// null continuation means that this method is not going to be resumed, and we don't need to
43+
// store the context
44+
if (continuation != null) {
45+
contextField.set(continuation, context);
46+
}
47+
return context;
48+
} else {
49+
return continuation != null ? contextField.get(continuation) : null;
50+
}
51+
}
52+
53+
public static Scope openScope(Context context) {
54+
return context != null ? context.makeCurrent() : null;
55+
}
56+
57+
public static void exitCoroutine(
58+
Object result,
59+
MethodRequest request,
60+
Continuation<?> continuation,
61+
Context context,
62+
Scope scope) {
63+
exitCoroutine(null, result, request, continuation, context, scope);
64+
}
65+
66+
public static void exitCoroutine(
67+
Throwable error,
68+
Object result,
69+
MethodRequest request,
70+
Continuation<?> continuation,
71+
Context context,
72+
Scope scope) {
73+
if (scope == null) {
74+
return;
75+
}
76+
scope.close();
77+
78+
// end the span when this method can not be resumed (coroutine is null) or if it has reached
79+
// final state (returns anything else besides COROUTINE_SUSPENDED)
80+
if (continuation == null || result != IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
81+
instrumenter().end(context, request, null, error);
82+
}
83+
}
84+
85+
public static void setSpanAttribute(int label, String name, boolean value) {
86+
// only add the attribute when coroutine is started
87+
if (label == 0) {
88+
Span.current().setAttribute(name, value);
89+
}
90+
}
91+
92+
public static void setSpanAttribute(int label, String name, byte value) {
93+
// only add the attribute when coroutine is started
94+
if (label == 0) {
95+
Span.current().setAttribute(name, value);
96+
}
97+
}
98+
99+
public static void setSpanAttribute(int label, String name, char value) {
100+
// only add the attribute when coroutine is started
101+
if (label == 0) {
102+
Span.current().setAttribute(name, String.valueOf(value));
103+
}
104+
}
105+
106+
public static void setSpanAttribute(int label, String name, double value) {
107+
// only add the attribute when coroutine is started
108+
if (label == 0) {
109+
Span.current().setAttribute(name, value);
110+
}
111+
}
112+
113+
public static void setSpanAttribute(int label, String name, float value) {
114+
// only add the attribute when coroutine is started
115+
if (label == 0) {
116+
Span.current().setAttribute(name, value);
117+
}
118+
}
119+
120+
public static void setSpanAttribute(int label, String name, int value) {
121+
// only add the attribute when coroutine is started
122+
if (label == 0) {
123+
Span.current().setAttribute(name, value);
124+
}
125+
}
126+
127+
public static void setSpanAttribute(int label, String name, long value) {
128+
// only add the attribute when coroutine is started
129+
if (label == 0) {
130+
Span.current().setAttribute(name, value);
131+
}
132+
}
133+
134+
public static void setSpanAttribute(int label, String name, short value) {
135+
// only add the attribute when coroutine is started
136+
if (label == 0) {
137+
Span.current().setAttribute(name, value);
138+
}
139+
}
140+
141+
public static void setSpanAttribute(int label, String name, Object value) {
142+
// only add the attribute when coroutine is started
143+
if (label != 0) {
144+
return;
145+
}
146+
if (value instanceof String) {
147+
Span.current().setAttribute(name, (String) value);
148+
} else if (value instanceof Boolean) {
149+
Span.current().setAttribute(name, (Boolean) value);
150+
} else if (value instanceof Byte) {
151+
Span.current().setAttribute(name, (Byte) value);
152+
} else if (value instanceof Character) {
153+
Span.current().setAttribute(name, (Character) value);
154+
} else if (value instanceof Double) {
155+
Span.current().setAttribute(name, (Double) value);
156+
} else if (value instanceof Float) {
157+
Span.current().setAttribute(name, (Float) value);
158+
} else if (value instanceof Integer) {
159+
Span.current().setAttribute(name, (Integer) value);
160+
} else if (value instanceof Long) {
161+
Span.current().setAttribute(name, (Long) value);
162+
}
163+
// TODO: arrays and List not supported see AttributeBindingFactoryTest
164+
}
165+
166+
public static void init() {}
167+
168+
private AnnotationInstrumentationHelper() {}
169+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.instrumentationannotations;
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+
/** Instrumentation for methods annotated with {@code WithSpan} annotation. */
18+
@AutoService(InstrumentationModule.class)
19+
public class AnnotationInstrumentationModule extends InstrumentationModule {
20+
21+
public AnnotationInstrumentationModule() {
22+
super(
23+
"kotlinx-coroutines-opentelemetry-instrumentation-annotations",
24+
"kotlinx-coroutines",
25+
"opentelemetry-instrumentation-annotations");
26+
}
27+
28+
@Override
29+
public int order() {
30+
// Run first to ensure other automatic instrumentation is added after and therefore is executed
31+
// earlier in the instrumented method and create the span to attach attributes to.
32+
return -1000;
33+
}
34+
35+
@Override
36+
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
37+
return hasClassesNamed(
38+
"application.io.opentelemetry.instrumentation.annotations.WithSpan",
39+
"kotlinx.coroutines.CoroutineContextKt");
40+
}
41+
42+
@Override
43+
public List<TypeInstrumentation> typeInstrumentations() {
44+
return singletonList(new WithSpanInstrumentation());
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.instrumentationannotations;
7+
8+
import io.opentelemetry.api.GlobalOpenTelemetry;
9+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
10+
import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesExtractor;
11+
import io.opentelemetry.instrumentation.api.instrumenter.util.SpanNames;
12+
13+
public final class AnnotationSingletons {
14+
15+
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.kotlinx-coroutines";
16+
17+
private static final Instrumenter<MethodRequest, Object> INSTRUMENTER = createInstrumenter();
18+
19+
public static Instrumenter<MethodRequest, Object> instrumenter() {
20+
return INSTRUMENTER;
21+
}
22+
23+
private static Instrumenter<MethodRequest, Object> createInstrumenter() {
24+
return Instrumenter.builder(
25+
GlobalOpenTelemetry.get(),
26+
INSTRUMENTATION_NAME,
27+
AnnotationSingletons::spanNameFromMethodRequest)
28+
.addAttributesExtractor(
29+
CodeAttributesExtractor.create(MethodRequestCodeAttributesGetter.INSTANCE))
30+
.buildInstrumenter(MethodRequest::getSpanKind);
31+
}
32+
33+
private static String spanNameFromMethodRequest(MethodRequest request) {
34+
String spanName = request.getWithSpanValue();
35+
if (spanName == null || spanName.isEmpty()) {
36+
spanName = SpanNames.fromMethod(request.getDeclaringClass(), request.getMethodName());
37+
}
38+
return spanName;
39+
}
40+
41+
private AnnotationSingletons() {}
42+
}

0 commit comments

Comments
 (0)