Skip to content

Commit

Permalink
Adds binding of attributes in methods annotated with @WithSpan (#3188)
Browse files Browse the repository at this point in the history
* Add binding of span attributes for traced methods

* Spring Aspect support, memoization

* Test with attribute name from ParameterNameDiscoverer

* Refactorings, javadocs and tests

* Refactor out creating AttributeBinding to separate class

* Wrapped attribute bindings for array parameters

* Attribute binding for List and EnumSet

* Attribute binding for subclass of List with reflection helper class

* Fix test failures in JDK 8

* Attribute binding to Set<? extends Enum>

* Move attribute binding to instrumentation-annotation-support project

* Fix copypastaed missing imports in tests

* Simplify ParameterizedClass based on PR feedback and add javadocs

* Remove blank javadoc

* Switch to cache with weak keys for memoizing attribute bindings by method

* Remove binding support for EnumSet<?>

* Clean up javadoc

* Use SpanAttribute annotation from opentelemetry 1.4.0-SNAPSHOT

* Address some PR concerns

* Clean up

* Fix instrumentation dependency in Springboot

* Update documentation

* Switch javaagent dep to compileOnly per PR comment

* spotless

* Kick CI

* Rename annotation support module and packages

* Resolve SpanAttribute annotation at runtime with fallback for older OTel versions

* Remove unnecessary dependency

* Move reflection helper to annotation support module

* Remove unnecessary enum and fields from unit test
  • Loading branch information
HaloFour authored Jul 14, 2021
1 parent cbfd7e1 commit 814239c
Show file tree
Hide file tree
Showing 28 changed files with 1,832 additions and 22 deletions.
23 changes: 23 additions & 0 deletions docs/manual-instrumentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,29 @@ Each time the application invokes the annotated method, it creates a span that d
and provides any thrown exceptions. Unless specified as an argument to the annotation, the span name
will be `<className>.<methodName>`.


## Adding attributes to the span with `@SpanAttribute`

When a span is created for an annotated method the values of the arguments to the method invocation
can be automatically added as attributes to the created span by annotating the method parameters
with the `@SpanAttribute` annotation.

```java
import io.opentelemetry.extension.annotations.SpanAttribute;
import io.opentelemetry.extension.annotations.WithSpan;

public class MyClass {
@WithSpan
public void MyLogic(@SpanAttribute("parameter1") String parameter1, @SpanAttribute("parameter2") long parameter2) {
<...>
}
}
```

Unless specified as an argument to the annotation, the attribute name will be derived from the
formal parameter names if they are compiled into the `.class` files by passing the `-parameters`
option to the `javac` compiler.

## Suppressing `@WithSpan` instrumentation

Suppressing `@WithSpan` is useful if you have code that is over-instrumented using `@WithSpan`
Expand Down
28 changes: 28 additions & 0 deletions instrumentation-api-annotation-support/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
plugins {
id("otel.java-conventions")
id("otel.jacoco-conventions")
id("otel.publish-conventions")
}

group = "io.opentelemetry.instrumentation"

dependencies {
implementation(project(":instrumentation-api"))

api("io.opentelemetry:opentelemetry-api")
api("io.opentelemetry:opentelemetry-semconv")

implementation("io.opentelemetry:opentelemetry-api-metrics")
implementation("org.slf4j:slf4j-api")

compileOnly("com.google.auto.value:auto-value-annotations")
annotationProcessor("com.google.auto.value:auto-value")

testImplementation(project(":testing-common"))
testImplementation("org.mockito:mockito-core")
testImplementation("org.mockito:mockito-junit-jupiter")
testImplementation("org.assertj:assertj-core")
testImplementation("org.awaitility:awaitility")
testImplementation("io.opentelemetry:opentelemetry-sdk-metrics")
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.annotation.support;

import java.lang.annotation.Annotation;
import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.function.Function;
import org.checkerframework.checker.nullness.qual.Nullable;

/** Helper class for reflecting over annotations at runtime.. */
public class AnnotationReflectionHelper {
private AnnotationReflectionHelper() {}

/**
* Returns the {@link Class Class&lt;? extends Annotation&gt;} for the name of the {@link
* Annotation} at runtime, otherwise returns {@code null}.
*/
@Nullable
public static Class<? extends Annotation> forNameOrNull(
ClassLoader classLoader, String className) {
try {
return Class.forName(className, true, classLoader).asSubclass(Annotation.class);
} catch (ClassNotFoundException | ClassCastException exception) {
return null;
}
}

/**
* Binds a lambda of the functional interface {@link Function Function&lt;A extends Annotation,
* T&gt;} to the element of an {@link Annotation} class by name which, when invoked with an
* instance of that annotation, will return the value of that element.
*
* <p>For example, calling this method as follows:
*
* <pre>{@code
* Function<WithSpan, String> function = AnnotationReflectionHelper.bindAnnotationElementMethod(
* MethodHandles.lookup(),
* WithSpan.class,
* "value",
* String.class);
* }</pre>
*
* <p>is equivalent to the following Java code:
*
* <pre>{@code
* Function<WithSpan, String> function = WithSpan::value;
* }</pre>
*
* @param lookup the {@link MethodHandles.Lookup} of the calling method, e.g. {@link
* MethodHandles#lookup()}
* @param annotationClass the {@link Class} of the {@link Annotation}
* @param methodName name of the annotation element method
* @param returnClass type of the annotation element
* @param <A> the type of the annotation
* @param <T> the type of the annotation element
* @return Instance of {@link Function Function&lt;Annotation, T&gt;} that is bound to the
* annotation element method
* @throws NoSuchMethodException the annotation element method was not found
* @throws Throwable on failing to bind to the
*/
public static <A extends Annotation, T> Function<A, T> bindAnnotationElementMethod(
MethodHandles.Lookup lookup,
Class<? extends Annotation> annotationClass,
String methodName,
Class<T> returnClass)
throws Throwable {

MethodHandle valueHandle =
lookup.findVirtual(annotationClass, methodName, MethodType.methodType(returnClass));

CallSite callSite =
LambdaMetafactory.metafactory(
lookup,
"apply",
MethodType.methodType(Function.class),
MethodType.methodType(Object.class, Object.class),
valueHandle,
MethodType.methodType(returnClass, annotationClass));

MethodHandle factory = callSite.getTarget();

@SuppressWarnings("unchecked")
Function<A, T> function = (Function<A, T>) factory.invoke();
return function;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.annotation.support;

import io.opentelemetry.instrumentation.api.tracer.AttributeSetter;

/** Represents the binding of a method parameter to an attribute of a traced method. */
@FunctionalInterface
public interface AttributeBinding {

/**
* Applies the value of the method argument as an attribute on the span for the traced method.
*
* @param setter the {@link AttributeSetter} onto which to add the attribute
* @param arg the value of the method argument
*/
void apply(AttributeSetter setter, Object arg);
}
Loading

0 comments on commit 814239c

Please sign in to comment.