Skip to content

Commit

Permalink
WithCurrentSpan annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
sfriberg committed Feb 10, 2023
1 parent 3d571c3 commit 1144ffc
Show file tree
Hide file tree
Showing 15 changed files with 474 additions and 158 deletions.
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
Comparing source compatibility of against
No changes.
+++ NEW ANNOTATION: PUBLIC(+) ABSTRACT(+) io.opentelemetry.instrumentation.annotations.WithCurrentSpan (not serializable)
+++ CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+++ NEW INTERFACE: java.lang.annotation.Annotation
+++ NEW SUPERCLASS: java.lang.Object
+++ NEW ANNOTATION: java.lang.annotation.Target
+++ NEW ELEMENT: value=java.lang.annotation.ElementType.METHOD,java.lang.annotation.ElementType.CONSTRUCTOR (+)
+++ NEW ANNOTATION: java.lang.annotation.Retention
+++ NEW ELEMENT: value=java.lang.annotation.RetentionPolicy.RUNTIME (+)
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
package io.opentelemetry.instrumentation.api.annotation.support;

import io.opentelemetry.api.common.AttributeKey;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.AbstractList;
import java.util.Arrays;
Expand Down Expand Up @@ -338,4 +340,42 @@ private static AttributeBinding defaultBinding(String name) {
AttributeKey<String> key = AttributeKey.stringKey(name);
return (setter, arg) -> setter.put(key, arg.toString());
}

/**
* Creates a binding of the parameters of the traced method to span attributes.
*
* @param method the traced method
* @return the bindings of the parameters
*/
static AttributeBindings bind(
Method method, ParameterAttributeNamesExtractor parameterAttributeNamesExtractor) {
AttributeBindings bindings = EmptyAttributeBindings.INSTANCE;

Parameter[] parameters = method.getParameters();
if (parameters.length == 0) {
return bindings;
}

String[] attributeNames = parameterAttributeNamesExtractor.extract(method, parameters);
if (attributeNames == null || attributeNames.length != parameters.length) {
return bindings;
}

for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
String attributeName = attributeNames[i];
if (attributeName == null || attributeName.isEmpty()) {
continue;
}

bindings =
new CombinedAttributeBindings(
bindings,
i,
AttributeBindingFactory.createBinding(
attributeName, parameter.getParameterizedType()));
}

return bindings;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

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

import io.opentelemetry.api.common.AttributesBuilder;

/** AttributeBindings implementation that is able to chain multiple AttributeBindings. */
final class CombinedAttributeBindings implements AttributeBindings {
private final AttributeBindings parent;
private final int index;
private final AttributeBinding binding;

public CombinedAttributeBindings(AttributeBindings parent, int index, AttributeBinding binding) {
this.parent = parent;
this.index = index;
this.binding = binding;
}

@Override
public boolean isEmpty() {
return false;
}

@Override
public void apply(AttributesBuilder target, Object[] args) {
parent.apply(target, args);
if (args != null && args.length > index) {
Object arg = args[index];
if (arg != null) {
binding.apply(target, arg);
}
}
}
}
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.api.common.AttributesBuilder;

/** Singleton empty implementation of AttributeBindings. */
enum EmptyAttributeBindings implements AttributeBindings {
INSTANCE;

@Override
public boolean isEmpty() {
return true;
}

@Override
public void apply(AttributesBuilder target, Object[] args) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.internal.cache.Cache;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import javax.annotation.Nullable;

/** Extractor of {@link io.opentelemetry.api.common.Attributes} for a traced method. */
Expand Down Expand Up @@ -48,7 +47,10 @@ public static <REQUEST, RESPONSE> MethodSpanAttributesExtractor<REQUEST, RESPONS
@Override
public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) {
Method method = methodExtractor.extract(request);
AttributeBindings bindings = cache.computeIfAbsent(method, this::bind);
AttributeBindings bindings =
cache.computeIfAbsent(
method,
(Method m) -> AttributeBindingFactory.bind(m, parameterAttributeNamesExtractor));
if (!bindings.isEmpty()) {
Object[] args = methodArgumentsExtractor.extract(request);
bindings.apply(attributes, args);
Expand All @@ -62,82 +64,4 @@ public void onEnd(
REQUEST request,
@Nullable RESPONSE response,
@Nullable Throwable error) {}

/**
* Creates a binding of the parameters of the traced method to span attributes.
*
* @param method the traced method
* @return the bindings of the parameters
*/
private AttributeBindings bind(Method method) {
AttributeBindings bindings = EmptyAttributeBindings.INSTANCE;

Parameter[] parameters = method.getParameters();
if (parameters.length == 0) {
return bindings;
}

String[] attributeNames = parameterAttributeNamesExtractor.extract(method, parameters);
if (attributeNames.length != parameters.length) {
return bindings;
}

for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
String attributeName = attributeNames[i];
if (attributeName == null || attributeName.isEmpty()) {
continue;
}

bindings =
new CombinedAttributeBindings(
bindings,
i,
AttributeBindingFactory.createBinding(
attributeName, parameter.getParameterizedType()));
}

return bindings;
}

protected enum EmptyAttributeBindings implements AttributeBindings {
INSTANCE;

@Override
public boolean isEmpty() {
return true;
}

@Override
public void apply(AttributesBuilder target, Object[] args) {}
}

private static final class CombinedAttributeBindings implements AttributeBindings {
private final AttributeBindings parent;
private final int index;
private final AttributeBinding binding;

public CombinedAttributeBindings(
AttributeBindings parent, int index, AttributeBinding binding) {
this.parent = parent;
this.index = index;
this.binding = binding;
}

@Override
public boolean isEmpty() {
return false;
}

@Override
public void apply(AttributesBuilder target, Object[] args) {
parent.apply(target, args);
if (args != null && args.length > index) {
Object arg = args[index];
if (arg != null) {
binding.apply(target, arg);
}
}
}
}
}
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.api.annotation.support;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.instrumentation.api.internal.cache.Cache;
import java.lang.reflect.Method;

/** Extractor of {@link io.opentelemetry.api.common.Attributes} for a traced method. */
public final class SpanAttributesExtractor {

private final Cache<Method, AttributeBindings> cache;
private final ParameterAttributeNamesExtractor parameterAttributeNamesExtractor;

public static SpanAttributesExtractor newInstance(
ParameterAttributeNamesExtractor parameterAttributeNamesExtractor) {
return new SpanAttributesExtractor(parameterAttributeNamesExtractor, new MethodCache<>());
}

SpanAttributesExtractor(
ParameterAttributeNamesExtractor parameterAttributeNamesExtractor,
Cache<Method, AttributeBindings> cache) {
this.parameterAttributeNamesExtractor = parameterAttributeNamesExtractor;
this.cache = cache;
}

public Attributes getAttributes(Method method, Object[] args) {
AttributesBuilder attributes = Attributes.builder();
AttributeBindings bindings =
cache.computeIfAbsent(
method,
(Method m) -> AttributeBindingFactory.bind(m, parameterAttributeNamesExtractor));
if (!bindings.isEmpty()) {
bindings.apply(attributes, args);
}
return attributes.build();
}
}
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.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* This annotation marks that an execution of this method or constructor is able to add attributes
* to the current span {@link io.opentelemetry.api.trace.Span}.
*
* <p>Application developers can use this annotation to signal OpenTelemetry auto-instrumentation
* that attributes annotated with the {@link
* io.opentelemetry.instrumentation.annotations.SpanAttribute} should be detected and added to the
* current span.
*
* <p>If you are a library developer, then probably you should NOT use this annotation, because it
* is non-functional without some form of auto-instrumentation.
*/
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
public @interface WithCurrentSpan {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.instrumentationannotations;

import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
import static net.bytebuddy.matcher.ElementMatchers.none;

import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.tooling.config.MethodsConfigurationParser;
import java.util.Map;
import java.util.Set;
import net.bytebuddy.description.ByteCodeElement;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;

public abstract class AnnotationInstrumentation implements TypeInstrumentation {

protected static final String TRACE_ANNOTATED_METHODS_EXCLUDE_CONFIG =
"otel.instrumentation.opentelemetry-instrumentation-annotations.exclude-methods";

/*
Returns a matcher for all methods that should be excluded from auto-instrumentation by
annotation-based advices.
*/
protected static ElementMatcher.Junction<MethodDescription> configureExcludedMethods() {
ElementMatcher.Junction<MethodDescription> result = none();

Map<String, Set<String>> excludedMethods =
MethodsConfigurationParser.parse(
InstrumentationConfig.get().getString(TRACE_ANNOTATED_METHODS_EXCLUDE_CONFIG));
for (Map.Entry<String, Set<String>> entry : excludedMethods.entrySet()) {
String className = entry.getKey();
ElementMatcher.Junction<ByteCodeElement> matcher =
isDeclaredBy(ElementMatchers.named(className));

Set<String> methodNames = entry.getValue();
if (!methodNames.isEmpty()) {
matcher = matcher.and(namedOneOf(methodNames.toArray(new String[0])));
}

result = result.or(matcher);
}

return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.instrumentationannotations;

import static java.util.Arrays.asList;

import application.io.opentelemetry.instrumentation.annotations.WithCurrentSpan;
import application.io.opentelemetry.instrumentation.annotations.WithSpan;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.List;

/**
* Instrumentation for methods annotated with {@link WithSpan} and {@link WithCurrentSpan}
* annotations.
*/
@AutoService(InstrumentationModule.class)
public class AnnotationInstrumentationModule extends InstrumentationModule {

public AnnotationInstrumentationModule() {
super("opentelemetry-instrumentation-annotations");
}

@Override
public int order() {
// Run first to ensure other automatic intstrumentation is added after and therefore is executed
// earlier in the instrumented method and create the span to attach attributes to.
return Integer.MIN_VALUE;
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return asList(new WithSpanInstrumentation(), new WithCurrentSpanInstrumentation());
}
}
Loading

0 comments on commit 1144ffc

Please sign in to comment.