-
Notifications
You must be signed in to change notification settings - Fork 879
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement @WithSpan support for kotlin coroutines (#8870)
Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
- Loading branch information
Showing
17 changed files
with
1,229 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
169 changes: 169 additions & 0 deletions
169
...ntation/kotlinxcoroutines/instrumentationannotations/AnnotationInstrumentationHelper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.instrumentationannotations; | ||
|
||
import static io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.instrumentationannotations.AnnotationSingletons.instrumenter; | ||
|
||
import io.opentelemetry.api.trace.Span; | ||
import io.opentelemetry.api.trace.SpanKind; | ||
import io.opentelemetry.context.Context; | ||
import io.opentelemetry.context.Scope; | ||
import io.opentelemetry.instrumentation.api.util.VirtualField; | ||
import kotlin.coroutines.Continuation; | ||
import kotlin.coroutines.intrinsics.IntrinsicsKt; | ||
|
||
public final class AnnotationInstrumentationHelper { | ||
|
||
private static final VirtualField<Continuation<?>, Context> contextField = | ||
VirtualField.find(Continuation.class, Context.class); | ||
|
||
public static MethodRequest createMethodRequest( | ||
Class<?> declaringClass, String methodName, String withSpanValue, String spanKindString) { | ||
SpanKind spanKind = SpanKind.INTERNAL; | ||
if (spanKindString != null) { | ||
try { | ||
spanKind = SpanKind.valueOf(spanKindString); | ||
} catch (IllegalArgumentException exception) { | ||
// ignore | ||
} | ||
} | ||
|
||
return MethodRequest.create(declaringClass, methodName, withSpanValue, spanKind); | ||
} | ||
|
||
public static Context enterCoroutine( | ||
int label, Continuation<?> continuation, MethodRequest request) { | ||
// label 0 means that coroutine is started, any other label means that coroutine is resumed | ||
if (label == 0) { | ||
Context context = instrumenter().start(Context.current(), request); | ||
// null continuation means that this method is not going to be resumed, and we don't need to | ||
// store the context | ||
if (continuation != null) { | ||
contextField.set(continuation, context); | ||
} | ||
return context; | ||
} else { | ||
return continuation != null ? contextField.get(continuation) : null; | ||
} | ||
} | ||
|
||
public static Scope openScope(Context context) { | ||
return context != null ? context.makeCurrent() : null; | ||
} | ||
|
||
public static void exitCoroutine( | ||
Object result, | ||
MethodRequest request, | ||
Continuation<?> continuation, | ||
Context context, | ||
Scope scope) { | ||
exitCoroutine(null, result, request, continuation, context, scope); | ||
} | ||
|
||
public static void exitCoroutine( | ||
Throwable error, | ||
Object result, | ||
MethodRequest request, | ||
Continuation<?> continuation, | ||
Context context, | ||
Scope scope) { | ||
if (scope == null) { | ||
return; | ||
} | ||
scope.close(); | ||
|
||
// end the span when this method can not be resumed (coroutine is null) or if it has reached | ||
// final state (returns anything else besides COROUTINE_SUSPENDED) | ||
if (continuation == null || result != IntrinsicsKt.getCOROUTINE_SUSPENDED()) { | ||
instrumenter().end(context, request, null, error); | ||
} | ||
} | ||
|
||
public static void setSpanAttribute(int label, String name, boolean value) { | ||
// only add the attribute when coroutine is started | ||
if (label == 0) { | ||
Span.current().setAttribute(name, value); | ||
} | ||
} | ||
|
||
public static void setSpanAttribute(int label, String name, byte value) { | ||
// only add the attribute when coroutine is started | ||
if (label == 0) { | ||
Span.current().setAttribute(name, value); | ||
} | ||
} | ||
|
||
public static void setSpanAttribute(int label, String name, char value) { | ||
// only add the attribute when coroutine is started | ||
if (label == 0) { | ||
Span.current().setAttribute(name, String.valueOf(value)); | ||
} | ||
} | ||
|
||
public static void setSpanAttribute(int label, String name, double value) { | ||
// only add the attribute when coroutine is started | ||
if (label == 0) { | ||
Span.current().setAttribute(name, value); | ||
} | ||
} | ||
|
||
public static void setSpanAttribute(int label, String name, float value) { | ||
// only add the attribute when coroutine is started | ||
if (label == 0) { | ||
Span.current().setAttribute(name, value); | ||
} | ||
} | ||
|
||
public static void setSpanAttribute(int label, String name, int value) { | ||
// only add the attribute when coroutine is started | ||
if (label == 0) { | ||
Span.current().setAttribute(name, value); | ||
} | ||
} | ||
|
||
public static void setSpanAttribute(int label, String name, long value) { | ||
// only add the attribute when coroutine is started | ||
if (label == 0) { | ||
Span.current().setAttribute(name, value); | ||
} | ||
} | ||
|
||
public static void setSpanAttribute(int label, String name, short value) { | ||
// only add the attribute when coroutine is started | ||
if (label == 0) { | ||
Span.current().setAttribute(name, value); | ||
} | ||
} | ||
|
||
public static void setSpanAttribute(int label, String name, Object value) { | ||
// only add the attribute when coroutine is started | ||
if (label != 0) { | ||
return; | ||
} | ||
if (value instanceof String) { | ||
Span.current().setAttribute(name, (String) value); | ||
} else if (value instanceof Boolean) { | ||
Span.current().setAttribute(name, (Boolean) value); | ||
} else if (value instanceof Byte) { | ||
Span.current().setAttribute(name, (Byte) value); | ||
} else if (value instanceof Character) { | ||
Span.current().setAttribute(name, (Character) value); | ||
} else if (value instanceof Double) { | ||
Span.current().setAttribute(name, (Double) value); | ||
} else if (value instanceof Float) { | ||
Span.current().setAttribute(name, (Float) value); | ||
} else if (value instanceof Integer) { | ||
Span.current().setAttribute(name, (Integer) value); | ||
} else if (value instanceof Long) { | ||
Span.current().setAttribute(name, (Long) value); | ||
} | ||
// TODO: arrays and List not supported see AttributeBindingFactoryTest | ||
} | ||
|
||
public static void init() {} | ||
|
||
private AnnotationInstrumentationHelper() {} | ||
} |
46 changes: 46 additions & 0 deletions
46
...ntation/kotlinxcoroutines/instrumentationannotations/AnnotationInstrumentationModule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.instrumentationannotations; | ||
|
||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; | ||
import static java.util.Collections.singletonList; | ||
|
||
import com.google.auto.service.AutoService; | ||
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; | ||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; | ||
import java.util.List; | ||
import net.bytebuddy.matcher.ElementMatcher; | ||
|
||
/** Instrumentation for methods annotated with {@code WithSpan} annotation. */ | ||
@AutoService(InstrumentationModule.class) | ||
public class AnnotationInstrumentationModule extends InstrumentationModule { | ||
|
||
public AnnotationInstrumentationModule() { | ||
super( | ||
"kotlinx-coroutines-opentelemetry-instrumentation-annotations", | ||
"kotlinx-coroutines", | ||
"opentelemetry-instrumentation-annotations"); | ||
} | ||
|
||
@Override | ||
public int order() { | ||
// Run first to ensure other automatic instrumentation is added after and therefore is executed | ||
// earlier in the instrumented method and create the span to attach attributes to. | ||
return -1000; | ||
} | ||
|
||
@Override | ||
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() { | ||
return hasClassesNamed( | ||
"application.io.opentelemetry.instrumentation.annotations.WithSpan", | ||
"kotlinx.coroutines.CoroutineContextKt"); | ||
} | ||
|
||
@Override | ||
public List<TypeInstrumentation> typeInstrumentations() { | ||
return singletonList(new WithSpanInstrumentation()); | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
...nt/instrumentation/kotlinxcoroutines/instrumentationannotations/AnnotationSingletons.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.instrumentationannotations; | ||
|
||
import io.opentelemetry.api.GlobalOpenTelemetry; | ||
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; | ||
import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesExtractor; | ||
import io.opentelemetry.instrumentation.api.instrumenter.util.SpanNames; | ||
|
||
public final class AnnotationSingletons { | ||
|
||
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.kotlinx-coroutines"; | ||
|
||
private static final Instrumenter<MethodRequest, Object> INSTRUMENTER = createInstrumenter(); | ||
|
||
public static Instrumenter<MethodRequest, Object> instrumenter() { | ||
return INSTRUMENTER; | ||
} | ||
|
||
private static Instrumenter<MethodRequest, Object> createInstrumenter() { | ||
return Instrumenter.builder( | ||
GlobalOpenTelemetry.get(), | ||
INSTRUMENTATION_NAME, | ||
AnnotationSingletons::spanNameFromMethodRequest) | ||
.addAttributesExtractor( | ||
CodeAttributesExtractor.create(MethodRequestCodeAttributesGetter.INSTANCE)) | ||
.buildInstrumenter(MethodRequest::getSpanKind); | ||
} | ||
|
||
private static String spanNameFromMethodRequest(MethodRequest request) { | ||
String spanName = request.getWithSpanValue(); | ||
if (spanName == null || spanName.isEmpty()) { | ||
spanName = SpanNames.fromMethod(request.getDeclaringClass(), request.getMethodName()); | ||
} | ||
return spanName; | ||
} | ||
|
||
private AnnotationSingletons() {} | ||
} |
Oops, something went wrong.