Skip to content

Commit d1b7356

Browse files
sfribergMateusz Rzeszutek
and
Mateusz Rzeszutek
authored
AddingSpanAttributes annotation (#7787)
Co-authored-by: Mateusz Rzeszutek <[email protected]>
1 parent 0f258c6 commit d1b7356

File tree

24 files changed

+1766
-1043
lines changed

24 files changed

+1766
-1043
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
11
Comparing source compatibility of against
2-
No changes.
2+
+++ NEW ANNOTATION: PUBLIC(+) ABSTRACT(+) io.opentelemetry.instrumentation.annotations.AddingSpanAttributes (not serializable)
3+
+++ CLASS FILE FORMAT VERSION: 52.0 <- n.a.
4+
+++ NEW INTERFACE: java.lang.annotation.Annotation
5+
+++ NEW SUPERCLASS: java.lang.Object
6+
+++ NEW ANNOTATION: java.lang.annotation.Target
7+
+++ NEW ELEMENT: value=java.lang.annotation.ElementType.METHOD,java.lang.annotation.ElementType.CONSTRUCTOR (+)
8+
+++ NEW ANNOTATION: java.lang.annotation.Retention
9+
+++ NEW ELEMENT: value=java.lang.annotation.RetentionPolicy.RUNTIME (+)

instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/AttributeBindings.java

+40
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
package io.opentelemetry.instrumentation.api.annotation.support;
77

88
import io.opentelemetry.api.common.AttributesBuilder;
9+
import java.lang.reflect.Method;
10+
import java.lang.reflect.Parameter;
911

1012
/** Represents the bindings of method parameters to attributes of a traced method. */
1113
interface AttributeBindings {
@@ -24,4 +26,42 @@ interface AttributeBindings {
2426
* @param args the method arguments
2527
*/
2628
void apply(AttributesBuilder target, Object[] args);
29+
30+
/**
31+
* Creates a binding of the parameters of the traced method to span attributes.
32+
*
33+
* @param method the traced method
34+
* @return the bindings of the parameters
35+
*/
36+
static AttributeBindings bind(
37+
Method method, ParameterAttributeNamesExtractor parameterAttributeNamesExtractor) {
38+
AttributeBindings bindings = EmptyAttributeBindings.INSTANCE;
39+
40+
Parameter[] parameters = method.getParameters();
41+
if (parameters.length == 0) {
42+
return bindings;
43+
}
44+
45+
String[] attributeNames = parameterAttributeNamesExtractor.extract(method, parameters);
46+
if (attributeNames == null || attributeNames.length != parameters.length) {
47+
return bindings;
48+
}
49+
50+
for (int i = 0; i < parameters.length; i++) {
51+
Parameter parameter = parameters[i];
52+
String attributeName = attributeNames[i];
53+
if (attributeName == null || attributeName.isEmpty()) {
54+
continue;
55+
}
56+
57+
bindings =
58+
new CombinedAttributeBindings(
59+
bindings,
60+
i,
61+
AttributeBindingFactory.createBinding(
62+
attributeName, parameter.getParameterizedType()));
63+
}
64+
65+
return bindings;
66+
}
2767
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.api.annotation.support;
7+
8+
import io.opentelemetry.api.common.AttributesBuilder;
9+
10+
/** AttributeBindings implementation that is able to chain multiple AttributeBindings. */
11+
final class CombinedAttributeBindings implements AttributeBindings {
12+
private final AttributeBindings parent;
13+
private final int index;
14+
private final AttributeBinding binding;
15+
16+
public CombinedAttributeBindings(AttributeBindings parent, int index, AttributeBinding binding) {
17+
this.parent = parent;
18+
this.index = index;
19+
this.binding = binding;
20+
}
21+
22+
@Override
23+
public boolean isEmpty() {
24+
return false;
25+
}
26+
27+
@Override
28+
public void apply(AttributesBuilder target, Object[] args) {
29+
parent.apply(target, args);
30+
if (args != null && args.length > index) {
31+
Object arg = args[index];
32+
if (arg != null) {
33+
binding.apply(target, arg);
34+
}
35+
}
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.api.annotation.support;
7+
8+
import io.opentelemetry.api.common.AttributesBuilder;
9+
10+
/** Singleton empty implementation of AttributeBindings. */
11+
enum EmptyAttributeBindings implements AttributeBindings {
12+
INSTANCE;
13+
14+
@Override
15+
public boolean isEmpty() {
16+
return true;
17+
}
18+
19+
@Override
20+
public void apply(AttributesBuilder target, Object[] args) {}
21+
}

instrumentation-annotations-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/MethodSpanAttributesExtractor.java

+4-81
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
1111
import io.opentelemetry.instrumentation.api.internal.cache.Cache;
1212
import java.lang.reflect.Method;
13-
import java.lang.reflect.Parameter;
1413
import javax.annotation.Nullable;
1514

1615
/** Extractor of {@link io.opentelemetry.api.common.Attributes} for a traced method. */
@@ -22,7 +21,7 @@ public final class MethodSpanAttributesExtractor<REQUEST, RESPONSE>
2221
private final Cache<Method, AttributeBindings> cache;
2322
private final ParameterAttributeNamesExtractor parameterAttributeNamesExtractor;
2423

25-
public static <REQUEST, RESPONSE> MethodSpanAttributesExtractor<REQUEST, RESPONSE> newInstance(
24+
public static <REQUEST, RESPONSE> MethodSpanAttributesExtractor<REQUEST, RESPONSE> create(
2625
MethodExtractor<REQUEST> methodExtractor,
2726
ParameterAttributeNamesExtractor parameterAttributeNamesExtractor,
2827
MethodArgumentsExtractor<REQUEST> methodArgumentsExtractor) {
@@ -48,7 +47,9 @@ public static <REQUEST, RESPONSE> MethodSpanAttributesExtractor<REQUEST, RESPONS
4847
@Override
4948
public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) {
5049
Method method = methodExtractor.extract(request);
51-
AttributeBindings bindings = cache.computeIfAbsent(method, this::bind);
50+
AttributeBindings bindings =
51+
cache.computeIfAbsent(
52+
method, (Method m) -> AttributeBindings.bind(m, parameterAttributeNamesExtractor));
5253
if (!bindings.isEmpty()) {
5354
Object[] args = methodArgumentsExtractor.extract(request);
5455
bindings.apply(attributes, args);
@@ -62,82 +63,4 @@ public void onEnd(
6263
REQUEST request,
6364
@Nullable RESPONSE response,
6465
@Nullable Throwable error) {}
65-
66-
/**
67-
* Creates a binding of the parameters of the traced method to span attributes.
68-
*
69-
* @param method the traced method
70-
* @return the bindings of the parameters
71-
*/
72-
private AttributeBindings bind(Method method) {
73-
AttributeBindings bindings = EmptyAttributeBindings.INSTANCE;
74-
75-
Parameter[] parameters = method.getParameters();
76-
if (parameters.length == 0) {
77-
return bindings;
78-
}
79-
80-
String[] attributeNames = parameterAttributeNamesExtractor.extract(method, parameters);
81-
if (attributeNames.length != parameters.length) {
82-
return bindings;
83-
}
84-
85-
for (int i = 0; i < parameters.length; i++) {
86-
Parameter parameter = parameters[i];
87-
String attributeName = attributeNames[i];
88-
if (attributeName == null || attributeName.isEmpty()) {
89-
continue;
90-
}
91-
92-
bindings =
93-
new CombinedAttributeBindings(
94-
bindings,
95-
i,
96-
AttributeBindingFactory.createBinding(
97-
attributeName, parameter.getParameterizedType()));
98-
}
99-
100-
return bindings;
101-
}
102-
103-
protected enum EmptyAttributeBindings implements AttributeBindings {
104-
INSTANCE;
105-
106-
@Override
107-
public boolean isEmpty() {
108-
return true;
109-
}
110-
111-
@Override
112-
public void apply(AttributesBuilder target, Object[] args) {}
113-
}
114-
115-
private static final class CombinedAttributeBindings implements AttributeBindings {
116-
private final AttributeBindings parent;
117-
private final int index;
118-
private final AttributeBinding binding;
119-
120-
public CombinedAttributeBindings(
121-
AttributeBindings parent, int index, AttributeBinding binding) {
122-
this.parent = parent;
123-
this.index = index;
124-
this.binding = binding;
125-
}
126-
127-
@Override
128-
public boolean isEmpty() {
129-
return false;
130-
}
131-
132-
@Override
133-
public void apply(AttributesBuilder target, Object[] args) {
134-
parent.apply(target, args);
135-
if (args != null && args.length > index) {
136-
Object arg = args[index];
137-
if (arg != null) {
138-
binding.apply(target, arg);
139-
}
140-
}
141-
}
142-
}
14366
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.api.annotation.support;
7+
8+
import io.opentelemetry.api.common.Attributes;
9+
import io.opentelemetry.api.common.AttributesBuilder;
10+
import io.opentelemetry.instrumentation.api.internal.cache.Cache;
11+
import java.lang.reflect.Method;
12+
13+
/** Extractor of {@link io.opentelemetry.api.common.Attributes} for a traced method. */
14+
public final class SpanAttributesExtractor {
15+
16+
private final Cache<Method, AttributeBindings> cache;
17+
private final ParameterAttributeNamesExtractor parameterAttributeNamesExtractor;
18+
19+
public static SpanAttributesExtractor create(
20+
ParameterAttributeNamesExtractor parameterAttributeNamesExtractor) {
21+
return new SpanAttributesExtractor(parameterAttributeNamesExtractor, new MethodCache<>());
22+
}
23+
24+
SpanAttributesExtractor(
25+
ParameterAttributeNamesExtractor parameterAttributeNamesExtractor,
26+
Cache<Method, AttributeBindings> cache) {
27+
this.parameterAttributeNamesExtractor = parameterAttributeNamesExtractor;
28+
this.cache = cache;
29+
}
30+
31+
public Attributes extract(Method method, Object[] args) {
32+
AttributesBuilder attributes = Attributes.builder();
33+
AttributeBindings bindings =
34+
cache.computeIfAbsent(
35+
method, (Method m) -> AttributeBindings.bind(m, parameterAttributeNamesExtractor));
36+
if (!bindings.isEmpty()) {
37+
bindings.apply(attributes, args);
38+
}
39+
return attributes.build();
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.annotations;
7+
8+
import java.lang.annotation.ElementType;
9+
import java.lang.annotation.Retention;
10+
import java.lang.annotation.RetentionPolicy;
11+
import java.lang.annotation.Target;
12+
13+
/**
14+
* This annotation marks that an execution of this method or constructor is able to add attributes
15+
* to the current span {@link io.opentelemetry.api.trace.Span}.
16+
*
17+
* <p>Application developers can use this annotation to signal OpenTelemetry auto-instrumentation
18+
* that attributes annotated with the {@link
19+
* io.opentelemetry.instrumentation.annotations.SpanAttribute} should be detected and added to the
20+
* current span.
21+
*
22+
* <p>If no span is currently active no new span will be created, and no attributes will be
23+
* extracted.
24+
*
25+
* <p>Similar to {@link
26+
* io.opentelemetry.api.trace.Span#setAttribute(io.opentelemetry.api.common.AttributeKey,
27+
* java.lang.Object) Span.setAttribute() } methods, if the key is already mapped to a value the old
28+
* value is replaced by the extracted value.
29+
*
30+
* <p>If you are a library developer, then probably you should NOT use this annotation, because it
31+
* is non-functional without some form of auto-instrumentation.
32+
*/
33+
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
34+
@Retention(RetentionPolicy.RUNTIME)
35+
public @interface AddingSpanAttributes {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.annotations;
7+
8+
import io.opentelemetry.api.trace.Span;
9+
10+
/**
11+
* This class is not a classical test. It's just a demonstration of possible usages of {@link
12+
* AddingSpanAttributes} annotation together with some explanations. The goal of this class is to
13+
* serve as an early detection system for inconvenient API and unintended API breakage.
14+
*/
15+
@SuppressWarnings("unused")
16+
public class AddingSpanAttributesUsageExamples {
17+
18+
/**
19+
* The current {@link Span} will be updated to contain the annotated method parameters as
20+
* attributes.
21+
*
22+
* @param attribute1 A span attribute with the default name of {@code attribute1}.
23+
* @param value A span attribute with the name "attribute2".
24+
*/
25+
@AddingSpanAttributes
26+
public void attributes(
27+
@SpanAttribute String attribute1, @SpanAttribute("attribute2") long value) {}
28+
}

instrumentation/opentelemetry-extension-annotations-1.0/javaagent/build.gradle.kts

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ dependencies {
2323
// see the comment in opentelemetry-api-1.0.gradle for more details
2424
compileOnly(project(":opentelemetry-ext-annotations-shaded-for-instrumenting", configuration = "shadow"))
2525

26+
// Used by byte-buddy but not brought in as a transitive dependency.
27+
compileOnly("com.google.code.findbugs:annotations")
28+
testCompileOnly("com.google.code.findbugs:annotations")
29+
2630
testImplementation("io.opentelemetry:opentelemetry-extension-annotations")
2731
testImplementation(project(":instrumentation-annotations-support"))
2832
testImplementation("net.bytebuddy:byte-buddy")

instrumentation/opentelemetry-extension-annotations-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/extensionannotations/WithSpanSingletons.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ private static Instrumenter<MethodRequest, Object> createInstrumenterWithAttribu
5050
.addAttributesExtractor(
5151
CodeAttributesExtractor.create(MethodRequestCodeAttributesGetter.INSTANCE))
5252
.addAttributesExtractor(
53-
MethodSpanAttributesExtractor.newInstance(
53+
MethodSpanAttributesExtractor.create(
5454
MethodRequest::method,
5555
WithSpanParameterAttributeNamesExtractor.INSTANCE,
5656
MethodRequest::args))

0 commit comments

Comments
 (0)