Skip to content
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

AddingSpanAttributes annotation #7787

Merged
merged 12 commits into from
Apr 24, 2023
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 {
mateuszrzeszutek marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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