Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
opentelemetry-instrumentation-api = { module = "io.opentelemetry.instrumentation:opentelemetry-instrumentation-api" }
opentelemetry-instrumentation-apiSemconv = { module = "io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator", version.ref = "opentelemetry-instrumentation-alpha" }
opentelemetry-instrumentation-okhttp = { module = "io.opentelemetry.instrumentation:opentelemetry-okhttp-3.0", version.ref = "opentelemetry-instrumentation-alpha" }
opentelemetry-instrumentation-annotations = { module = "io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations", version.ref = "opentelemetry-instrumentation-alpha" }
opentelemetry-semconv = { module = "io.opentelemetry.semconv:opentelemetry-semconv", version.ref = "opentelemetry-semconv" }
opentelemetry-semconv-incubating = { module = "io.opentelemetry.semconv:opentelemetry-semconv-incubating", version.ref = "opentelemetry-semconv-alpha" }
opentelemetry-api = { module = "io.opentelemetry:opentelemetry-api" }
Expand Down
1 change: 1 addition & 0 deletions instrumentation/span-annotation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
placeholder
30 changes: 30 additions & 0 deletions instrumentation/span-annotation/agent/api/agent.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
public final class io/opentelemetry/instrumentation/agent/spanannotation/SpanAnnotationPlugin : net/bytebuddy/build/Plugin {
public fun <init> ()V
public fun apply (Lnet/bytebuddy/dynamic/DynamicType$Builder;Lnet/bytebuddy/description/type/TypeDescription;Lnet/bytebuddy/dynamic/ClassFileLocator;)Lnet/bytebuddy/dynamic/DynamicType$Builder;
public fun close ()V
public synthetic fun matches (Ljava/lang/Object;)Z
public fun matches (Lnet/bytebuddy/description/type/TypeDescription;)Z
}

public final class io/opentelemetry/instrumentation/agent/spanannotation/SpanAnnotationPluginKt {
public static final field ADDING_SPAN_ATTRIBUTES_ANNOTATION Ljava/lang/String;
public static final field SPAN_ATTRIBUTE_ANNOTATION Ljava/lang/String;
public static final field WITH_SPAN_ANNOTATION Ljava/lang/String;
}

public final class io/opentelemetry/instrumentation/agent/spanannotation/advice/method/AddingSpanAttributesMethodAdvice {
public static final field INSTANCE Lio/opentelemetry/instrumentation/agent/spanannotation/advice/method/AddingSpanAttributesMethodAdvice;
public static final fun onEnter ([Ljava/lang/Object;Ljava/lang/String;)V
}

public final class io/opentelemetry/instrumentation/agent/spanannotation/advice/method/SpanAttributeMethodAdvice {
public static final field INSTANCE Lio/opentelemetry/instrumentation/agent/spanannotation/advice/method/SpanAttributeMethodAdvice;
public static final fun onEnter ([Ljava/lang/Object;Ljava/lang/reflect/Method;)V
}

public final class io/opentelemetry/instrumentation/agent/spanannotation/advice/method/WithSpanMethodAdvice {
public static final field INSTANCE Lio/opentelemetry/instrumentation/agent/spanannotation/advice/method/WithSpanMethodAdvice;
public static final fun onEnter (Ljava/lang/reflect/Method;)Lkotlin/Pair;
public static final fun onExit (Lkotlin/Pair;Ljava/lang/Throwable;)V
}

18 changes: 18 additions & 0 deletions instrumentation/span-annotation/agent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
plugins {
id("otel.android-library-conventions")
id("otel.publish-conventions")
}

description = "placeholder"

android {
namespace = "io.opentelemetry.android.spanannotation.agent"
}

dependencies {
implementation(libs.opentelemetry.api)
implementation(libs.opentelemetry.context)
implementation(libs.opentelemetry.instrumentation.annotations)
implementation(project(":instrumentation:span-annotation:library"))
implementation(libs.byteBuddy)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.agent.spanannotation

import io.opentelemetry.instrumentation.agent.spanannotation.advice.method.AddingSpanAttributesMethodAdvice
import io.opentelemetry.instrumentation.agent.spanannotation.advice.method.SpanAttributeMethodAdvice
import io.opentelemetry.instrumentation.agent.spanannotation.advice.method.WithSpanMethodAdvice
import net.bytebuddy.asm.Advice
import net.bytebuddy.build.Plugin
import net.bytebuddy.description.type.TypeDescription
import net.bytebuddy.dynamic.ClassFileLocator
import net.bytebuddy.dynamic.DynamicType
import net.bytebuddy.matcher.ElementMatchers

const val WITH_SPAN_ANNOTATION = "io.opentelemetry.instrumentation.annotations.WithSpan"
const val SPAN_ATTRIBUTE_ANNOTATION = "io.opentelemetry.instrumentation.annotations.SpanAttribute"
const val ADDING_SPAN_ATTRIBUTES_ANNOTATION = "io.opentelemetry.instrumentation.annotations.AddingSpanAttributes"

class SpanAnnotationPlugin : Plugin {
override fun apply(
builder: DynamicType.Builder<*>,
typeDescription: TypeDescription,
classFileLocator: ClassFileLocator,
): DynamicType.Builder<*> =
builder
// Apply advice to methods annotated with @WithSpan
.visit(
Advice
.to(WithSpanMethodAdvice::class.java)
.on(
ElementMatchers
.not(ElementMatchers.isConstructor())
.and(
ElementMatchers.isAnnotatedWith(
ElementMatchers.named(WITH_SPAN_ANNOTATION),
),
),
),
)
// Apply advice to methods annotated with @AddingSpanAttributes
.visit(
Advice
.to(AddingSpanAttributesMethodAdvice::class.java)
.on(
ElementMatchers
.not(ElementMatchers.isConstructor())
.and(
ElementMatchers.isAnnotatedWith(
ElementMatchers.named(ADDING_SPAN_ATTRIBUTES_ANNOTATION),
),
),
),
)
// Apply advice to methods with parameters annotated with @SpanAttribute
.visit(
Advice
.to(SpanAttributeMethodAdvice::class.java)
.on(
ElementMatchers
.not(ElementMatchers.isConstructor())
.and(
ElementMatchers.hasParameters(
ElementMatchers.whereAny(
ElementMatchers.isAnnotatedWith(
ElementMatchers.named(SPAN_ATTRIBUTE_ANNOTATION),
),
),
),
),
),
)

override fun matches(target: TypeDescription?): Boolean =
target?.declaredMethods?.any { method ->
method.declaredAnnotations.any { annotation ->
annotation.annotationType.name == WITH_SPAN_ANNOTATION ||
annotation.annotationType.name == ADDING_SPAN_ATTRIBUTES_ANNOTATION
}
} == true

override fun close() {
// Nothing here yet?
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.agent.spanannotation.advice.method

import io.opentelemetry.api.trace.Span
import io.opentelemetry.instrumentation.library.spanannotation.HelperFunctions
import net.bytebuddy.asm.Advice
import java.lang.reflect.Method

object AddingSpanAttributesMethodAdvice {
@JvmStatic
@Advice.OnMethodEnter(suppress = Throwable::class)
fun onEnter(
@Advice.AllArguments args: Array<Any?>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not faimilar with @WithSpan, though I'm curious why it might need all the method's arguments?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found the @AddingSpanAttributes annotation but it seems i might've misunderstood it's function.
It is not mentioned in the linked documentation so perhaps I should remove this as well and stick to @WithSpan and @SpanAttribute ? It covers all the most common use cases already.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I also don't see it in the docs so it should be fine to leave it out, at least for now. I'll cc @breedx-splk in case he's more familiar with it to make sure we're not missing something important.

@Advice.Origin("#m") methodName: String,
) {
HelperFunctions.argsAsAttributes(
Span.current(),
args,
methodName,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.agent.spanannotation.advice.method

import io.opentelemetry.api.trace.Span
import io.opentelemetry.instrumentation.library.spanannotation.HelperFunctions
import net.bytebuddy.asm.Advice
import java.lang.reflect.Method

object SpanAttributeMethodAdvice {
@JvmStatic
@Advice.OnMethodEnter(suppress = Throwable::class)
fun onEnter(
@Advice.AllArguments args: Array<Any?>,
@Advice.Origin method: Method,
) {
HelperFunctions.argAsAttribute(
Span.current(),
method.parameterAnnotations,
args,
method.name,
)
}
}
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.instrumentation.agent.spanannotation.advice.method

import io.opentelemetry.api.trace.Span
import io.opentelemetry.context.Scope
import io.opentelemetry.instrumentation.annotations.WithSpan
import io.opentelemetry.instrumentation.library.spanannotation.HelperFunctions
import net.bytebuddy.asm.Advice
import java.lang.reflect.Method

object WithSpanMethodAdvice {
@JvmStatic
@Advice.OnMethodEnter(suppress = Throwable::class)
fun onEnter(
@Advice.Origin method: Method,
): Pair<Span, Scope> {
val withSpan =
method.getAnnotation(WithSpan::class.java)
?: error("WithSpan annotation not found on method ${method.name}")

return HelperFunctions.startSpan(
withSpan,
method.name,
)
}

@JvmStatic
@Advice.OnMethodExit(suppress = Throwable::class, onThrowable = Throwable::class)
fun onExit(
@Advice.Enter spanPair: Pair<Span, Scope>,
@Advice.Thrown throwable: Throwable?,
) {
HelperFunctions.stopSpan(
spanPair,
throwable,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.opentelemetry.instrumentation.agent.spanannotation.SpanAnnotationPlugin
21 changes: 21 additions & 0 deletions instrumentation/span-annotation/library/api/library.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
public final class io/opentelemetry/instrumentation/library/spanannotation/HelperFunctions {
public static final field INSTANCE Lio/opentelemetry/instrumentation/library/spanannotation/HelperFunctions;
public static final fun argAsAttribute (Lio/opentelemetry/api/trace/Span;[[Ljava/lang/annotation/Annotation;[Ljava/lang/Object;Ljava/lang/String;)V
public static final fun argsAsAttributes (Lio/opentelemetry/api/trace/Span;[Ljava/lang/Object;Ljava/lang/String;)V
public static final fun startSpan (Lio/opentelemetry/instrumentation/annotations/WithSpan;Ljava/lang/String;)Lkotlin/Pair;
public static final fun stopSpan (Lkotlin/Pair;Ljava/lang/Throwable;)V
}

public final class io/opentelemetry/instrumentation/library/spanannotation/SpanAnnotationInstrumentation : io/opentelemetry/android/instrumentation/AndroidInstrumentation {
public static final field Companion Lio/opentelemetry/instrumentation/library/spanannotation/SpanAnnotationInstrumentation$Companion;
public static field tracer Lio/opentelemetry/api/trace/Tracer;
public fun <init> ()V
public fun getName ()Ljava/lang/String;
public fun install (Lio/opentelemetry/android/instrumentation/InstallationContext;)V
}

public final class io/opentelemetry/instrumentation/library/spanannotation/SpanAnnotationInstrumentation$Companion {
public final fun getTracer ()Lio/opentelemetry/api/trace/Tracer;
public final fun setTracer (Lio/opentelemetry/api/trace/Tracer;)V
}

17 changes: 17 additions & 0 deletions instrumentation/span-annotation/library/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
plugins {
id("otel.android-library-conventions")
id("otel.publish-conventions")
}

description = "placeholder"

android {
namespace = "io.opentelemetry.android.spanannotation.library"
}

dependencies {
implementation(libs.opentelemetry.api)
implementation(libs.opentelemetry.context)
implementation(libs.opentelemetry.instrumentation.annotations)
api(project(":instrumentation:android-instrumentation"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.library.spanannotation

import io.opentelemetry.api.trace.Span
import io.opentelemetry.context.Scope
import io.opentelemetry.instrumentation.annotations.SpanAttribute
import io.opentelemetry.instrumentation.annotations.WithSpan
import kotlin.text.ifEmpty

object HelperFunctions {
@JvmStatic
fun startSpan(
withSpan: WithSpan,
name: String,
): Pair<Span, Scope> {
val spanBuilder =
SpanAnnotationInstrumentation
.tracer
.spanBuilder(withSpan.value.ifEmpty { name })
.setSpanKind(withSpan.kind)

if (!withSpan.inheritContext) {
spanBuilder.setNoParent()
}

val span = spanBuilder.startSpan()
val scope = span.makeCurrent()

return Pair(span, scope)
}

@JvmStatic
fun stopSpan(
spanPair: Pair<Span, Scope>,
throwable: Throwable?,
) {
spanPair.let { (span, scope) ->
throwable?.let {
span.recordException(throwable)
span.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, it.message ?: "Exception thrown")
}
scope.close()
span.end()
}
}

@JvmStatic
fun argAsAttribute(
span: Span,
parameterAnnotations: Array<Array<Annotation>>,
args: Array<Any?>,
name: String,
) {
args.forEachIndexed { index, arg ->
parameterAnnotations[index]
.filterIsInstance<SpanAttribute>()
.firstOrNull()
?.let { spanAttribute ->
val attributeKey = spanAttribute.value.takeIf { it.isNotEmpty() } ?: "arg${index}_$name"
span.setAttribute(attributeKey, arg.toString())
}
}
}

@JvmStatic
fun argsAsAttributes(
span: Span,
args: Array<Any?>,
name: String,
) {
args.forEachIndexed { index, arg ->
val attributeKey = "arg${index}_$name"
span.setAttribute(attributeKey, arg.toString())
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.library.spanannotation

import com.google.auto.service.AutoService
import io.opentelemetry.android.instrumentation.AndroidInstrumentation
import io.opentelemetry.android.instrumentation.InstallationContext
import io.opentelemetry.api.trace.Tracer

@AutoService(AndroidInstrumentation::class)
class SpanAnnotationInstrumentation : AndroidInstrumentation {
override val name: String = "span-annotation"

override fun install(ctx: InstallationContext) {
tracer =
ctx.openTelemetry
.tracerProvider
.tracerBuilder("io.opentelemetry.android.instrumentation.span-annotation")
.build()
}

companion object {
lateinit var tracer: Tracer
}
}
Loading