Skip to content

Commit 799aca2

Browse files
authored
Allow method instrumentation trace methods in boot loader (#12454)
1 parent 238a201 commit 799aca2

File tree

5 files changed

+62
-4
lines changed

5 files changed

+62
-4
lines changed

instrumentation/methods/javaagent/build.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ dependencies {
1515

1616
tasks.withType<Test>().configureEach {
1717
jvmArgs(
18-
"-Dotel.instrumentation.methods.include=io.opentelemetry.javaagent.instrumentation.methods.MethodTest\$ConfigTracedCallable[call];io.opentelemetry.javaagent.instrumentation.methods.MethodTest\$ConfigTracedCompletableFuture[getResult]"
18+
"-Dotel.instrumentation.methods.include=io.opentelemetry.javaagent.instrumentation.methods.MethodTest\$ConfigTracedCallable[call];io.opentelemetry.javaagent.instrumentation.methods.MethodTest\$ConfigTracedCompletableFuture[getResult];javax.naming.directory.InitialDirContext[search]"
1919
)
2020
}

instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodInstrumentation.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
99
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
1010
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType;
11+
import static io.opentelemetry.javaagent.instrumentation.methods.MethodSingletons.getBootstrapLoader;
1112
import static io.opentelemetry.javaagent.instrumentation.methods.MethodSingletons.instrumenter;
1213
import static net.bytebuddy.matcher.ElementMatchers.named;
1314
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
@@ -36,7 +37,15 @@ public MethodInstrumentation(String className, Set<String> methodNames) {
3637

3738
@Override
3839
public ElementMatcher<ClassLoader> classLoaderOptimization() {
39-
return hasClassesNamed(className);
40+
ElementMatcher<ClassLoader> delegate = hasClassesNamed(className);
41+
return target -> {
42+
// hasClassesNamed does not support null class loader, so we provide a custom loader that
43+
// can be used to look up resources in bootstrap loader
44+
if (target == null) {
45+
target = getBootstrapLoader();
46+
}
47+
return delegate.matches(target);
48+
};
4049
}
4150

4251
@Override

instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodInstrumentationModule.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
package io.opentelemetry.javaagent.instrumentation.methods;
77

88
import static java.util.Collections.emptyList;
9-
import static java.util.Collections.singletonList;
109

1110
import com.google.auto.service.AutoService;
1211
import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig;
1312
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
1413
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
1514
import io.opentelemetry.javaagent.tooling.config.MethodsConfigurationParser;
15+
import java.util.Arrays;
1616
import java.util.List;
1717
import java.util.Map;
1818
import java.util.Set;
@@ -45,7 +45,9 @@ public MethodInstrumentationModule() {
4545
public List<String> getAdditionalHelperClassNames() {
4646
return typeInstrumentations.isEmpty()
4747
? emptyList()
48-
: singletonList("io.opentelemetry.javaagent.instrumentation.methods.MethodSingletons");
48+
: Arrays.asList(
49+
"io.opentelemetry.javaagent.instrumentation.methods.MethodSingletons",
50+
"io.opentelemetry.javaagent.instrumentation.methods.MethodSingletons$BootstrapLoader");
4951
}
5052

5153
@Override

instrumentation/methods/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/methods/MethodSingletons.java

+15
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public final class MethodSingletons {
1717
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.methods";
1818

1919
private static final Instrumenter<ClassAndMethod, Void> INSTRUMENTER;
20+
private static final ClassLoader bootstrapLoader = new BootstrapLoader();
2021

2122
static {
2223
CodeAttributesGetter<ClassAndMethod> codeAttributesGetter =
@@ -35,5 +36,19 @@ public static Instrumenter<ClassAndMethod, Void> instrumenter() {
3536
return INSTRUMENTER;
3637
}
3738

39+
public static ClassLoader getBootstrapLoader() {
40+
return bootstrapLoader;
41+
}
42+
3843
private MethodSingletons() {}
44+
45+
private static class BootstrapLoader extends ClassLoader {
46+
static {
47+
ClassLoader.registerAsParallelCapable();
48+
}
49+
50+
BootstrapLoader() {
51+
super(null);
52+
}
53+
}
3954
}

instrumentation/methods/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/methods/MethodTest.java

+32
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
1010
import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_FUNCTION;
1111
import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_NAMESPACE;
12+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1213

1314
import io.opentelemetry.api.trace.Span;
1415
import io.opentelemetry.api.trace.SpanKind;
@@ -18,6 +19,10 @@
1819
import java.util.concurrent.CompletableFuture;
1920
import java.util.concurrent.CountDownLatch;
2021
import java.util.concurrent.TimeUnit;
22+
import java.util.concurrent.atomic.AtomicReference;
23+
import javax.naming.NoInitialContextException;
24+
import javax.naming.directory.InitialDirContext;
25+
import javax.naming.ldap.InitialLdapContext;
2126
import org.junit.jupiter.api.Test;
2227
import org.junit.jupiter.api.extension.RegisterExtension;
2328

@@ -40,6 +45,33 @@ void methodTraced() {
4045
equalTo(CODE_FUNCTION, "call"))));
4146
}
4247

48+
@Test
49+
void bootLoaderMethodTraced() throws Exception {
50+
InitialLdapContext context = new InitialLdapContext();
51+
AtomicReference<Throwable> throwableReference = new AtomicReference<>();
52+
assertThatThrownBy(
53+
() -> {
54+
try {
55+
context.search("foo", null);
56+
} catch (Throwable throwable) {
57+
throwableReference.set(throwable);
58+
throw throwable;
59+
}
60+
})
61+
.isInstanceOf(NoInitialContextException.class);
62+
63+
testing.waitAndAssertTraces(
64+
trace ->
65+
trace.hasSpansSatisfyingExactly(
66+
span ->
67+
span.hasName("InitialDirContext.search")
68+
.hasKind(SpanKind.INTERNAL)
69+
.hasException(throwableReference.get())
70+
.hasAttributesSatisfyingExactly(
71+
equalTo(CODE_NAMESPACE, InitialDirContext.class.getName()),
72+
equalTo(CODE_FUNCTION, "search"))));
73+
}
74+
4375
static class ConfigTracedCallable implements Callable<String> {
4476

4577
@Override

0 commit comments

Comments
 (0)