-
Notifications
You must be signed in to change notification settings - Fork 874
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
Implement @WithSpan support for kotlin coroutines #8870
Changes from 2 commits
5758775
e3facae
ed1e229
f005234
7c5c108
2808d42
7e96ae2
f923062
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you know of any documentation/kotlin design doc that'd explain the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://www.youtube.com/watch?v=YrrUCSi72E8 was useful if I remember correctly (around 7:30 there is something about labels). Basically it should work so that when coroutine is suspended it returns the label where it should be resumed and on resume it is called with the same label. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! I'll watch that |
||
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() {} | ||
} |
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()); | ||
} | ||
} |
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() {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.instrumentationannotations; | ||
|
||
import io.opentelemetry.api.trace.SpanKind; | ||
|
||
public final class MethodRequest { | ||
private final Class<?> declaringClass; | ||
private final String methodName; | ||
private final String withSpanValue; | ||
private final SpanKind spanKind; | ||
|
||
private MethodRequest( | ||
Class<?> declaringClass, String methodName, String withSpanValue, SpanKind spanKind) { | ||
this.declaringClass = declaringClass; | ||
this.methodName = methodName; | ||
this.withSpanValue = withSpanValue; | ||
this.spanKind = spanKind; | ||
} | ||
|
||
public static MethodRequest create( | ||
Class<?> declaringClass, String methodName, String withSpanValue, SpanKind spanKind) { | ||
return new MethodRequest(declaringClass, methodName, withSpanValue, spanKind); | ||
} | ||
|
||
public Class<?> getDeclaringClass() { | ||
return declaringClass; | ||
} | ||
|
||
public String getMethodName() { | ||
return methodName; | ||
} | ||
|
||
public String getWithSpanValue() { | ||
return withSpanValue; | ||
} | ||
|
||
public SpanKind getSpanKind() { | ||
return spanKind; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.instrumentationannotations; | ||
|
||
import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesGetter; | ||
|
||
enum MethodRequestCodeAttributesGetter implements CodeAttributesGetter<MethodRequest> { | ||
INSTANCE; | ||
|
||
@Override | ||
public Class<?> getCodeClass(MethodRequest methodRequest) { | ||
return methodRequest.getDeclaringClass(); | ||
} | ||
|
||
@Override | ||
public String getMethodName(MethodRequest methodRequest) { | ||
return methodRequest.getMethodName(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we also keep the original muzzle definition and use
excludeInstrumentationName
, to make sure the kotlin instrumentation is still applied in the absence of these dependencies on the user classpath?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or possibly extract out into separate module?