Skip to content

Commit

Permalink
AddingSpanAttributes annotation (#7787)
Browse files Browse the repository at this point in the history
Co-authored-by: Mateusz Rzeszutek <[email protected]>
  • Loading branch information
sfriberg and Mateusz Rzeszutek authored Apr 24, 2023
1 parent 0f258c6 commit d1b7356
Show file tree
Hide file tree
Showing 24 changed files with 1,766 additions and 1,043 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.AddingSpanAttributes (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.AttributesBuilder;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/** Represents the bindings of method parameters to attributes of a traced method. */
interface AttributeBindings {
Expand All @@ -24,4 +26,42 @@ interface AttributeBindings {
* @param args the method arguments
*/
void apply(AttributesBuilder target, Object[] args);

/**
* 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 All @@ -22,7 +21,7 @@ public final class MethodSpanAttributesExtractor<REQUEST, RESPONSE>
private final Cache<Method, AttributeBindings> cache;
private final ParameterAttributeNamesExtractor parameterAttributeNamesExtractor;

public static <REQUEST, RESPONSE> MethodSpanAttributesExtractor<REQUEST, RESPONSE> newInstance(
public static <REQUEST, RESPONSE> MethodSpanAttributesExtractor<REQUEST, RESPONSE> create(
MethodExtractor<REQUEST> methodExtractor,
ParameterAttributeNamesExtractor parameterAttributeNamesExtractor,
MethodArgumentsExtractor<REQUEST> methodArgumentsExtractor) {
Expand All @@ -48,7 +47,9 @@ 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) -> AttributeBindings.bind(m, parameterAttributeNamesExtractor));
if (!bindings.isEmpty()) {
Object[] args = methodArgumentsExtractor.extract(request);
bindings.apply(attributes, args);
Expand All @@ -62,82 +63,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,41 @@
/*
* 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 create(
ParameterAttributeNamesExtractor parameterAttributeNamesExtractor) {
return new SpanAttributesExtractor(parameterAttributeNamesExtractor, new MethodCache<>());
}

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

public Attributes extract(Method method, Object[] args) {
AttributesBuilder attributes = Attributes.builder();
AttributeBindings bindings =
cache.computeIfAbsent(
method, (Method m) -> AttributeBindings.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,35 @@
/*
* 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 no span is currently active no new span will be created, and no attributes will be
* extracted.
*
* <p>Similar to {@link
* io.opentelemetry.api.trace.Span#setAttribute(io.opentelemetry.api.common.AttributeKey,
* java.lang.Object) Span.setAttribute() } methods, if the key is already mapped to a value the old
* value is replaced by the extracted value.
*
* <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 AddingSpanAttributes {}
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.annotations;

import io.opentelemetry.api.trace.Span;

/**
* This class is not a classical test. It's just a demonstration of possible usages of {@link
* AddingSpanAttributes} annotation together with some explanations. The goal of this class is to
* serve as an early detection system for inconvenient API and unintended API breakage.
*/
@SuppressWarnings("unused")
public class AddingSpanAttributesUsageExamples {

/**
* The current {@link Span} will be updated to contain the annotated method parameters as
* attributes.
*
* @param attribute1 A span attribute with the default name of {@code attribute1}.
* @param value A span attribute with the name "attribute2".
*/
@AddingSpanAttributes
public void attributes(
@SpanAttribute String attribute1, @SpanAttribute("attribute2") long value) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ dependencies {
// see the comment in opentelemetry-api-1.0.gradle for more details
compileOnly(project(":opentelemetry-ext-annotations-shaded-for-instrumenting", configuration = "shadow"))

// Used by byte-buddy but not brought in as a transitive dependency.
compileOnly("com.google.code.findbugs:annotations")
testCompileOnly("com.google.code.findbugs:annotations")

testImplementation("io.opentelemetry:opentelemetry-extension-annotations")
testImplementation(project(":instrumentation-annotations-support"))
testImplementation("net.bytebuddy:byte-buddy")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ private static Instrumenter<MethodRequest, Object> createInstrumenterWithAttribu
.addAttributesExtractor(
CodeAttributesExtractor.create(MethodRequestCodeAttributesGetter.INSTANCE))
.addAttributesExtractor(
MethodSpanAttributesExtractor.newInstance(
MethodSpanAttributesExtractor.create(
MethodRequest::method,
WithSpanParameterAttributeNamesExtractor.INSTANCE,
MethodRequest::args))
Expand Down
Loading

0 comments on commit d1b7356

Please sign in to comment.