Skip to content

Commit fb784aa

Browse files
authored
Support new annotations (and change of instrumentation name for opentelemetry-annotations) (#6296)
* Support new annotations * Consistency * Simplify * Annotation * oops
1 parent 8adebaa commit fb784aa

File tree

40 files changed

+1166
-139
lines changed

40 files changed

+1166
-139
lines changed

conventions/src/main/kotlin/otel.java-conventions.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ configurations.configureEach {
376376
dependencySubstitution {
377377
substitute(module("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api")).using(project(":instrumentation-api"))
378378
substitute(module("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-semconv")).using(project(":instrumentation-api-semconv"))
379+
substitute(module("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations")).using(project(":instrumentation-annotations"))
379380
substitute(module("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations-support")).using(project(":instrumentation-annotations-support"))
380381
substitute(module("io.opentelemetry.instrumentation:opentelemetry-instrumentation-appender-api-internal")).using(project(":instrumentation-appender-api-internal"))
381382
substitute(module("io.opentelemetry.javaagent:opentelemetry-javaagent-bootstrap")).using(project(":javaagent-bootstrap"))

instrumentation/opentelemetry-annotations-1.0/testing/build.gradle.kts instrumentation-annotations-support-testing/build.gradle.kts

-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,4 @@ plugins {
44

55
dependencies {
66
api(project(":testing-common"))
7-
8-
implementation("io.opentelemetry:opentelemetry-extension-annotations")
97
}

instrumentation/guava-10.0/javaagent/build.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ dependencies {
2424

2525
implementation(project(":instrumentation:guava-10.0:library"))
2626

27-
testImplementation(project(":instrumentation:opentelemetry-annotations-1.0:testing"))
27+
testImplementation(project(":instrumentation-annotations-support-testing"))
2828
testImplementation("io.opentelemetry:opentelemetry-extension-annotations")
2929
}

instrumentation/guava-10.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/guava/GuavaWithSpanTest.java instrumentation/guava-10.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/guava/BaseGuavaWithSpanTest.java

+1-30
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,12 @@
88
import com.google.common.util.concurrent.Futures;
99
import com.google.common.util.concurrent.ListenableFuture;
1010
import com.google.common.util.concurrent.SettableFuture;
11-
import io.opentelemetry.extension.annotations.WithSpan;
12-
import io.opentelemetry.javaagent.instrumentation.otelannotations.AbstractTraced;
1311
import io.opentelemetry.javaagent.instrumentation.otelannotations.AbstractWithSpanTest;
1412
import org.testcontainers.shaded.com.google.common.base.Throwables;
1513

16-
class GuavaWithSpanTest
14+
abstract class BaseGuavaWithSpanTest
1715
extends AbstractWithSpanTest<SettableFuture<String>, ListenableFuture<String>> {
1816

19-
@Override
20-
protected AbstractTraced<SettableFuture<String>, ListenableFuture<String>> newTraced() {
21-
return new Traced();
22-
}
23-
2417
@Override
2518
protected void complete(SettableFuture<String> future, String value) {
2619
future.set(value);
@@ -50,26 +43,4 @@ protected Throwable unwrapError(Throwable t) {
5043
protected String canceledKey() {
5144
return "guava.canceled";
5245
}
53-
54-
static final class Traced
55-
extends AbstractTraced<SettableFuture<String>, ListenableFuture<String>> {
56-
57-
@Override
58-
@WithSpan
59-
protected SettableFuture<String> completable() {
60-
return SettableFuture.create();
61-
}
62-
63-
@Override
64-
@WithSpan
65-
protected ListenableFuture<String> alreadySucceeded() {
66-
return Futures.immediateFuture("Value");
67-
}
68-
69-
@Override
70-
@WithSpan
71-
protected ListenableFuture<String> alreadyFailed() {
72-
return Futures.immediateFailedFuture(FAILURE);
73-
}
74-
}
7546
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.guava;
7+
8+
import com.google.common.util.concurrent.Futures;
9+
import com.google.common.util.concurrent.ListenableFuture;
10+
import com.google.common.util.concurrent.SettableFuture;
11+
import io.opentelemetry.extension.annotations.WithSpan;
12+
import io.opentelemetry.javaagent.instrumentation.otelannotations.AbstractTraced;
13+
14+
class ExtensionAnnotationsGuavaWithSpanTest extends BaseGuavaWithSpanTest {
15+
16+
@Override
17+
protected AbstractTraced<SettableFuture<String>, ListenableFuture<String>> newTraced() {
18+
return new Traced();
19+
}
20+
21+
static final class Traced
22+
extends AbstractTraced<SettableFuture<String>, ListenableFuture<String>> {
23+
24+
@Override
25+
@WithSpan
26+
protected SettableFuture<String> completable() {
27+
return SettableFuture.create();
28+
}
29+
30+
@Override
31+
@WithSpan
32+
protected ListenableFuture<String> alreadySucceeded() {
33+
return Futures.immediateFuture("Value");
34+
}
35+
36+
@Override
37+
@WithSpan
38+
protected ListenableFuture<String> alreadyFailed() {
39+
return Futures.immediateFailedFuture(FAILURE);
40+
}
41+
}
42+
}

instrumentation/opentelemetry-annotations-1.0/README.md

-5
This file was deleted.

instrumentation/opentelemetry-api/opentelemetry-api-1.0/javaagent/build.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,5 @@ dependencies {
4141

4242
// @WithSpan annotation is used to generate spans in ContextBridgeTest
4343
testImplementation("io.opentelemetry:opentelemetry-extension-annotations")
44-
testInstrumentation(project(":instrumentation:opentelemetry-annotations-1.0:javaagent"))
44+
testInstrumentation(project(":instrumentation:opentelemetry-extension-annotations-1.0:javaagent"))
4545
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Settings for the OpenTelemetry Extension Annotations integration
2+
3+
| Environment variable | Type | Default | Description |
4+
|----------------- |------ |--------- |------------- |
5+
| `otel.instrumentation.opentelemetry-annotations.exclude-methods` | String | | All methods to be excluded from auto-instrumentation by annotation-based advices. |
+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
package io.opentelemetry.javaagent.instrumentation.otelannotations;
6+
package io.opentelemetry.javaagent.instrumentation.extensionannotations;
77

88
import java.lang.reflect.Method;
99

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.extensionannotations;
7+
8+
import static net.bytebuddy.matcher.ElementMatchers.declaresMethod;
9+
import static net.bytebuddy.matcher.ElementMatchers.hasParameters;
10+
import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
11+
import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
12+
import static net.bytebuddy.matcher.ElementMatchers.named;
13+
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
14+
import static net.bytebuddy.matcher.ElementMatchers.none;
15+
import static net.bytebuddy.matcher.ElementMatchers.not;
16+
import static net.bytebuddy.matcher.ElementMatchers.whereAny;
17+
18+
import io.opentelemetry.context.Context;
19+
import io.opentelemetry.context.Scope;
20+
import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndSupport;
21+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
22+
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
23+
import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig;
24+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
25+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
26+
import io.opentelemetry.javaagent.tooling.config.MethodsConfigurationParser;
27+
import java.lang.reflect.Method;
28+
import java.util.Map;
29+
import java.util.Set;
30+
import net.bytebuddy.asm.Advice;
31+
import net.bytebuddy.description.ByteCodeElement;
32+
import net.bytebuddy.description.annotation.AnnotationSource;
33+
import net.bytebuddy.description.method.MethodDescription;
34+
import net.bytebuddy.description.type.TypeDescription;
35+
import net.bytebuddy.implementation.bytecode.assign.Assigner;
36+
import net.bytebuddy.matcher.ElementMatcher;
37+
import net.bytebuddy.matcher.ElementMatchers;
38+
39+
public class WithSpanInstrumentation implements TypeInstrumentation {
40+
41+
private static final String TRACE_ANNOTATED_METHODS_EXCLUDE_CONFIG =
42+
"otel.instrumentation.opentelemetry-annotations.exclude-methods";
43+
44+
private final ElementMatcher.Junction<AnnotationSource> annotatedMethodMatcher;
45+
private final ElementMatcher.Junction<MethodDescription> annotatedParametersMatcher;
46+
// this matcher matches all methods that should be excluded from transformation
47+
private final ElementMatcher.Junction<MethodDescription> excludedMethodsMatcher;
48+
49+
WithSpanInstrumentation() {
50+
annotatedMethodMatcher =
51+
isAnnotatedWith(named("application.io.opentelemetry.extension.annotations.WithSpan"));
52+
annotatedParametersMatcher =
53+
hasParameters(
54+
whereAny(
55+
isAnnotatedWith(
56+
named("application.io.opentelemetry.extension.annotations.SpanAttribute"))));
57+
excludedMethodsMatcher = configureExcludedMethods();
58+
}
59+
60+
@Override
61+
public ElementMatcher<TypeDescription> typeMatcher() {
62+
return declaresMethod(annotatedMethodMatcher);
63+
}
64+
65+
@Override
66+
public void transform(TypeTransformer transformer) {
67+
ElementMatcher.Junction<MethodDescription> tracedMethods =
68+
annotatedMethodMatcher.and(not(excludedMethodsMatcher));
69+
70+
ElementMatcher.Junction<MethodDescription> tracedMethodsWithParameters =
71+
tracedMethods.and(annotatedParametersMatcher);
72+
ElementMatcher.Junction<MethodDescription> tracedMethodsWithoutParameters =
73+
tracedMethods.and(not(annotatedParametersMatcher));
74+
75+
transformer.applyAdviceToMethod(
76+
tracedMethodsWithoutParameters,
77+
WithSpanInstrumentation.class.getName() + "$WithSpanAdvice");
78+
79+
// Only apply advice for tracing parameters as attributes if any of the parameters are annotated
80+
// with @SpanAttribute to avoid unnecessarily copying the arguments into an array.
81+
transformer.applyAdviceToMethod(
82+
tracedMethodsWithParameters,
83+
WithSpanInstrumentation.class.getName() + "$WithSpanAttributesAdvice");
84+
}
85+
86+
/*
87+
Returns a matcher for all methods that should be excluded from auto-instrumentation by
88+
annotation-based advices.
89+
*/
90+
static ElementMatcher.Junction<MethodDescription> configureExcludedMethods() {
91+
ElementMatcher.Junction<MethodDescription> result = none();
92+
93+
Map<String, Set<String>> excludedMethods =
94+
MethodsConfigurationParser.parse(
95+
InstrumentationConfig.get().getString(TRACE_ANNOTATED_METHODS_EXCLUDE_CONFIG));
96+
for (Map.Entry<String, Set<String>> entry : excludedMethods.entrySet()) {
97+
String className = entry.getKey();
98+
ElementMatcher.Junction<ByteCodeElement> matcher =
99+
isDeclaredBy(ElementMatchers.named(className));
100+
101+
Set<String> methodNames = entry.getValue();
102+
if (!methodNames.isEmpty()) {
103+
matcher = matcher.and(namedOneOf(methodNames.toArray(new String[0])));
104+
}
105+
106+
result = result.or(matcher);
107+
}
108+
109+
return result;
110+
}
111+
112+
@SuppressWarnings("unused")
113+
public static class WithSpanAdvice {
114+
115+
@Advice.OnMethodEnter(suppress = Throwable.class)
116+
public static void onEnter(
117+
@Advice.Origin Method originMethod,
118+
@Advice.Local("otelMethod") Method method,
119+
@Advice.Local("otelContext") Context context,
120+
@Advice.Local("otelScope") Scope scope) {
121+
// Every usage of @Advice.Origin Method is replaced with a call to Class.getMethod, copy it
122+
// to local variable so that there would be only one call to Class.getMethod.
123+
method = originMethod;
124+
125+
Instrumenter<Method, Object> instrumenter = WithSpanSingletons.instrumenter();
126+
Context current = Java8BytecodeBridge.currentContext();
127+
128+
if (instrumenter.shouldStart(current, method)) {
129+
context = instrumenter.start(current, method);
130+
scope = context.makeCurrent();
131+
}
132+
}
133+
134+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
135+
public static void stopSpan(
136+
@Advice.Local("otelMethod") Method method,
137+
@Advice.Local("otelContext") Context context,
138+
@Advice.Local("otelScope") Scope scope,
139+
@Advice.Return(typing = Assigner.Typing.DYNAMIC, readOnly = false) Object returnValue,
140+
@Advice.Thrown Throwable throwable) {
141+
if (scope == null) {
142+
return;
143+
}
144+
scope.close();
145+
146+
AsyncOperationEndSupport<Method, Object> operationEndSupport =
147+
AsyncOperationEndSupport.create(
148+
WithSpanSingletons.instrumenter(), Object.class, method.getReturnType());
149+
returnValue = operationEndSupport.asyncEnd(context, method, returnValue, throwable);
150+
}
151+
}
152+
153+
@SuppressWarnings("unused")
154+
public static class WithSpanAttributesAdvice {
155+
156+
@Advice.OnMethodEnter(suppress = Throwable.class)
157+
public static void onEnter(
158+
@Advice.Origin Method originMethod,
159+
@Advice.Local("otelMethod") Method method,
160+
@Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] args,
161+
@Advice.Local("otelRequest") MethodRequest request,
162+
@Advice.Local("otelContext") Context context,
163+
@Advice.Local("otelScope") Scope scope) {
164+
165+
// Every usage of @Advice.Origin Method is replaced with a call to Class.getMethod, copy it
166+
// to local variable so that there would be only one call to Class.getMethod.
167+
method = originMethod;
168+
169+
Instrumenter<MethodRequest, Object> instrumenter =
170+
WithSpanSingletons.instrumenterWithAttributes();
171+
Context current = Java8BytecodeBridge.currentContext();
172+
request = new MethodRequest(method, args);
173+
174+
if (instrumenter.shouldStart(current, request)) {
175+
context = instrumenter.start(current, request);
176+
scope = context.makeCurrent();
177+
}
178+
}
179+
180+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
181+
public static void stopSpan(
182+
@Advice.Local("otelMethod") Method method,
183+
@Advice.Local("otelRequest") MethodRequest request,
184+
@Advice.Local("otelContext") Context context,
185+
@Advice.Local("otelScope") Scope scope,
186+
@Advice.Return(typing = Assigner.Typing.DYNAMIC, readOnly = false) Object returnValue,
187+
@Advice.Thrown Throwable throwable) {
188+
if (scope == null) {
189+
return;
190+
}
191+
scope.close();
192+
AsyncOperationEndSupport<MethodRequest, Object> operationEndSupport =
193+
AsyncOperationEndSupport.create(
194+
WithSpanSingletons.instrumenterWithAttributes(),
195+
Object.class,
196+
method.getReturnType());
197+
returnValue = operationEndSupport.asyncEnd(context, request, returnValue, throwable);
198+
}
199+
}
200+
}
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
package io.opentelemetry.javaagent.instrumentation.otelannotations;
6+
package io.opentelemetry.javaagent.instrumentation.extensionannotations;
77

88
import static java.util.Collections.singletonList;
99

@@ -18,7 +18,7 @@
1818
public class WithSpanInstrumentationModule extends InstrumentationModule {
1919

2020
public WithSpanInstrumentationModule() {
21-
super("opentelemetry-annotations");
21+
super("opentelemetry-extension-annotations");
2222
}
2323

2424
@Override
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
package io.opentelemetry.javaagent.instrumentation.otelannotations;
6+
package io.opentelemetry.javaagent.instrumentation.extensionannotations;
77

88
import io.opentelemetry.instrumentation.api.annotation.support.AnnotationReflectionHelper;
99
import io.opentelemetry.instrumentation.api.annotation.support.ParameterAttributeNamesExtractor;
@@ -43,7 +43,6 @@ private static Function<Annotation, String> resolveSpanAttributeValue(
4343
}
4444

4545
@Override
46-
@Nullable
4746
public String[] extract(Method method, Parameter[] parameters) {
4847
String[] attributeNames = new String[parameters.length];
4948
for (int i = 0; i < parameters.length; i++) {
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
package io.opentelemetry.javaagent.instrumentation.otelannotations;
6+
package io.opentelemetry.javaagent.instrumentation.extensionannotations;
77

88
import static java.util.logging.Level.FINE;
99

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Settings for the OpenTelemetry Instrumentation Annotations integration
2+
3+
| Environment variable | Type | Default | Description |
4+
|----------------- |------ |--------- |------------- |
5+
| `otel.instrumentation.opentelemetry-annotations.exclude-methods` | String | | All methods to be excluded from auto-instrumentation by annotation-based advices. |

0 commit comments

Comments
 (0)