Skip to content

Commit 8addc67

Browse files
authored
Make global ignore matchers configurable (#2035)
1 parent 11ad122 commit 8addc67

File tree

5 files changed

+131
-17
lines changed

5 files changed

+131
-17
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.spi;
7+
8+
import net.bytebuddy.description.type.TypeDescription;
9+
10+
/**
11+
* {@link IgnoreMatcherProvider} can be used to ignore (or allow) types (e.g. classes or
12+
* classloaders) from being instrumented. OpenTelemetry agent by default ignores specific set of
13+
* classes (e.g. {@code org.gradle.*}) and classloaders. This is mainly done to improve startup
14+
* time, but also to explicitly disable instrumentation of a specific types (e.g. other agents). An
15+
* implementation of this class can be used to override this behaviour.
16+
*
17+
* <p>This is a service provider interface that requires implementations to be registered in {@code
18+
* META-INF/services} folder. Only a single implementation of this SPI can be provided.
19+
*/
20+
public interface IgnoreMatcherProvider {
21+
22+
/**
23+
* Whether to ignore (or allow) type. This method is called for every class, therefore the
24+
* implementation has to be as efficient as possible.
25+
*
26+
* @param target a class.
27+
* @return the result of the ignore evaluation.
28+
*/
29+
Result type(TypeDescription target);
30+
31+
/**
32+
* Whether to ignore (or allow) classloader. This method is called for every classloader,
33+
* therefore the implementation has to be as efficient as possible.
34+
*
35+
* @param classLoader a classloader.
36+
* @return the result of the ignore evaluation.
37+
*/
38+
Result classloader(ClassLoader classLoader);
39+
40+
/** Result of the ignore evaluation. */
41+
enum Result {
42+
/** Default - delegate the evaluation to global ignore matchers from javaagent-tooling. */
43+
DEFAULT,
44+
/** Ignore instrumentation for a type. */
45+
IGNORE,
46+
/** Allow instrumentation for a type. */
47+
ALLOW
48+
}
49+
}

javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import io.opentelemetry.javaagent.spi.BootstrapPackagesProvider;
2121
import io.opentelemetry.javaagent.spi.ByteBuddyAgentCustomizer;
2222
import io.opentelemetry.javaagent.spi.ComponentInstaller;
23+
import io.opentelemetry.javaagent.spi.IgnoreMatcherProvider;
2324
import io.opentelemetry.javaagent.tooling.config.ConfigInitializer;
2425
import io.opentelemetry.javaagent.tooling.context.FieldBackedProvider;
2526
import io.opentelemetry.javaagent.tooling.matcher.GlobalClassloaderIgnoresMatcher;
@@ -29,6 +30,7 @@
2930
import java.util.Collections;
3031
import java.util.Comparator;
3132
import java.util.HashMap;
33+
import java.util.Iterator;
3234
import java.util.List;
3335
import java.util.Map;
3436
import java.util.ServiceLoader;
@@ -97,6 +99,10 @@ public static ResettableClassFileTransformer installBytebuddyAgent(
9799

98100
FieldBackedProvider.resetContextMatchers();
99101

102+
IgnoreMatcherProvider ignoreMatcherProvider = loadIgnoreMatcherProvider();
103+
log.debug(
104+
"Ignore matcher provider {} will be used", ignoreMatcherProvider.getClass().getName());
105+
100106
AgentBuilder.Ignored ignoredAgentBuilder =
101107
new AgentBuilder.Default()
102108
.disableClassFormatChanges()
@@ -109,12 +115,13 @@ public static ResettableClassFileTransformer installBytebuddyAgent(
109115
// FIXME: we cannot enable it yet due to BB/JVM bug, see
110116
// https://github.com/raphw/byte-buddy/issues/558
111117
// .with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED)
112-
.ignore(any(), GlobalClassloaderIgnoresMatcher.skipClassLoader());
118+
.ignore(any(), GlobalClassloaderIgnoresMatcher.skipClassLoader(ignoreMatcherProvider));
113119

114120
ignoredAgentBuilder =
115121
ignoredAgentBuilder.or(
116122
globalIgnoresMatcher(
117-
Config.get().getBooleanProperty(DISABLE_GLOBAL_LIBRARY_IGNORES_FOR_TEST, false)));
123+
Config.get().getBooleanProperty(DISABLE_GLOBAL_LIBRARY_IGNORES_FOR_TEST, false),
124+
ignoreMatcherProvider));
118125

119126
ignoredAgentBuilder = ignoredAgentBuilder.or(matchesConfiguredExcludes());
120127

@@ -203,6 +210,17 @@ private static Iterable<ComponentInstaller> loadComponentProviders() {
203210
return ServiceLoader.load(ComponentInstaller.class, AgentInstaller.class.getClassLoader());
204211
}
205212

213+
private static IgnoreMatcherProvider loadIgnoreMatcherProvider() {
214+
ServiceLoader<IgnoreMatcherProvider> ignoreMatcherProviders =
215+
ServiceLoader.load(IgnoreMatcherProvider.class, AgentInstaller.class.getClassLoader());
216+
217+
Iterator<IgnoreMatcherProvider> iterator = ignoreMatcherProviders.iterator();
218+
if (iterator.hasNext()) {
219+
return iterator.next();
220+
}
221+
return new NoopIgnoreMatcherProvider();
222+
}
223+
206224
private static Iterable<ByteBuddyAgentCustomizer> loadByteBuddyAgentCustomizers() {
207225
return ServiceLoader.load(
208226
ByteBuddyAgentCustomizer.class, AgentInstaller.class.getClassLoader());
@@ -547,5 +565,17 @@ private static void logVersionInfo() {
547565
AgentInstaller.class.getName() + " loaded on " + AgentInstaller.class.getClassLoader());
548566
}
549567

568+
private static class NoopIgnoreMatcherProvider implements IgnoreMatcherProvider {
569+
@Override
570+
public Result classloader(ClassLoader classLoader) {
571+
return Result.DEFAULT;
572+
}
573+
574+
@Override
575+
public Result type(TypeDescription target) {
576+
return Result.DEFAULT;
577+
}
578+
}
579+
550580
private AgentInstaller() {}
551581
}

javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/matcher/GlobalClassloaderIgnoresMatcher.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import io.opentelemetry.javaagent.bootstrap.PatchLogger;
99
import io.opentelemetry.javaagent.bootstrap.WeakCache;
10+
import io.opentelemetry.javaagent.spi.IgnoreMatcherProvider;
1011
import io.opentelemetry.javaagent.tooling.AgentTooling;
1112
import io.opentelemetry.javaagent.tooling.bytebuddy.matcher.ClassLoaderMatcher;
1213
import net.bytebuddy.matcher.ElementMatcher;
@@ -17,23 +18,36 @@ public class GlobalClassloaderIgnoresMatcher
1718
extends ElementMatcher.Junction.AbstractBase<ClassLoader> {
1819
private static final Logger log = LoggerFactory.getLogger(GlobalClassloaderIgnoresMatcher.class);
1920

20-
private static final GlobalClassloaderIgnoresMatcher INSTANCE =
21-
new GlobalClassloaderIgnoresMatcher();
2221
/* Cache of classloader-instance -> (true|false). True = skip instrumentation. False = safe to instrument. */
2322
private static final String AGENT_CLASSLOADER_NAME =
2423
"io.opentelemetry.javaagent.bootstrap.AgentClassLoader";
2524
private static final String EXPORTER_CLASSLOADER_NAME =
2625
"io.opentelemetry.javaagent.tooling.ExporterClassLoader";
2726
private static final WeakCache<ClassLoader, Boolean> skipCache = AgentTooling.newWeakCache();
2827

29-
private GlobalClassloaderIgnoresMatcher() {}
28+
public static ElementMatcher.Junction.AbstractBase<ClassLoader> skipClassLoader(
29+
IgnoreMatcherProvider ignoreMatcherProvider) {
30+
return new GlobalClassloaderIgnoresMatcher(ignoreMatcherProvider);
31+
}
32+
33+
private final IgnoreMatcherProvider ignoreMatcherProviders;
3034

31-
public static ElementMatcher.Junction.AbstractBase<ClassLoader> skipClassLoader() {
32-
return INSTANCE;
35+
private GlobalClassloaderIgnoresMatcher(IgnoreMatcherProvider ignoreMatcherProviders) {
36+
this.ignoreMatcherProviders = ignoreMatcherProviders;
3337
}
3438

3539
@Override
3640
public boolean matches(ClassLoader cl) {
41+
IgnoreMatcherProvider.Result ignoreResult = ignoreMatcherProviders.classloader(cl);
42+
switch (ignoreResult) {
43+
case IGNORE:
44+
return true;
45+
case ALLOW:
46+
return false;
47+
case DEFAULT:
48+
default:
49+
}
50+
3751
if (cl == ClassLoaderMatcher.BOOTSTRAP_CLASSLOADER) {
3852
// Don't skip bootstrap loader
3953
return false;

javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/matcher/GlobalIgnoresMatcher.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
package io.opentelemetry.javaagent.tooling.matcher;
77

8+
import io.opentelemetry.javaagent.spi.IgnoreMatcherProvider;
89
import java.util.regex.Pattern;
910
import net.bytebuddy.description.type.TypeDescription;
1011
import net.bytebuddy.matcher.ElementMatcher;
@@ -32,16 +33,19 @@ public class GlobalIgnoresMatcher<T extends TypeDescription>
3233
Pattern.compile("com\\.mchange\\.v2\\.c3p0\\..*Proxy");
3334

3435
public static <T extends TypeDescription> ElementMatcher.Junction<T> globalIgnoresMatcher(
35-
boolean skipAdditionalLibraryMatcher) {
36-
return new GlobalIgnoresMatcher<>(skipAdditionalLibraryMatcher);
36+
boolean skipAdditionalLibraryMatcher, IgnoreMatcherProvider ignoreMatcherProviders) {
37+
return new GlobalIgnoresMatcher<>(skipAdditionalLibraryMatcher, ignoreMatcherProviders);
3738
}
3839

3940
private final ElementMatcher<T> additionalLibraryIgnoreMatcher =
4041
AdditionalLibraryIgnoresMatcher.additionalLibraryIgnoresMatcher();
4142
private final boolean skipAdditionalLibraryMatcher;
43+
private final IgnoreMatcherProvider ignoreMatcherProvider;
4244

43-
private GlobalIgnoresMatcher(boolean skipAdditionalLibraryMatcher) {
45+
private GlobalIgnoresMatcher(
46+
boolean skipAdditionalLibraryMatcher, IgnoreMatcherProvider ignoreMatcherProvider) {
4447
this.skipAdditionalLibraryMatcher = skipAdditionalLibraryMatcher;
48+
this.ignoreMatcherProvider = ignoreMatcherProvider;
4549
}
4650

4751
/**
@@ -51,6 +55,16 @@ private GlobalIgnoresMatcher(boolean skipAdditionalLibraryMatcher) {
5155
*/
5256
@Override
5357
public boolean matches(T target) {
58+
IgnoreMatcherProvider.Result ignoreResult = ignoreMatcherProvider.type(target);
59+
switch (ignoreResult) {
60+
case IGNORE:
61+
return true;
62+
case ALLOW:
63+
return false;
64+
case DEFAULT:
65+
default:
66+
}
67+
5468
String name = target.getActualName();
5569

5670
if (name.startsWith("jdk.internal.net.http.")) {

javaagent-tooling/src/test/groovy/io/opentelemetry/javaagent/test/ClassLoaderMatcherTest.groovy renamed to javaagent-tooling/src/test/groovy/io/opentelemetry/javaagent/tooling/matcher/ClassLoaderMatcherTest.groovy

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,49 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
package io.opentelemetry.javaagent.test
6+
package io.opentelemetry.javaagent.tooling.matcher
77

88
import io.opentelemetry.javaagent.bootstrap.AgentClassLoader
9-
import io.opentelemetry.javaagent.tooling.bytebuddy.matcher.ClassLoaderMatcher
109
import io.opentelemetry.javaagent.tooling.ExporterClassLoader
11-
import io.opentelemetry.javaagent.tooling.matcher.GlobalClassloaderIgnoresMatcher
10+
import io.opentelemetry.javaagent.spi.IgnoreMatcherProvider
1211
import spock.lang.Specification
1312

1413
class ClassLoaderMatcherTest extends Specification {
1514

15+
private final IgnoreMatcherProvider matcherProvider = [classloader: {cl -> IgnoreMatcherProvider.Result.DEFAULT}] as IgnoreMatcherProvider
16+
1617
def "skips agent classloader"() {
1718
setup:
1819
URL root = new URL("file://")
1920
URLClassLoader agentLoader = new AgentClassLoader(root, null, null)
2021
expect:
21-
GlobalClassloaderIgnoresMatcher.skipClassLoader().matches(agentLoader)
22+
GlobalClassloaderIgnoresMatcher.skipClassLoader(matcherProvider).matches(agentLoader)
2223
}
2324

2425
def "skips exporter classloader"() {
2526
setup:
2627
URL url = new URL("file://")
2728
URLClassLoader exporterLoader = new ExporterClassLoader(url, null)
2829
expect:
29-
GlobalClassloaderIgnoresMatcher.skipClassLoader().matches(exporterLoader)
30+
GlobalClassloaderIgnoresMatcher.skipClassLoader(matcherProvider).matches(exporterLoader)
3031
}
3132

3233
def "does not skip empty classloader"() {
3334
setup:
3435
ClassLoader emptyLoader = new ClassLoader() {}
3536
expect:
36-
!GlobalClassloaderIgnoresMatcher.skipClassLoader().matches(emptyLoader)
37+
!GlobalClassloaderIgnoresMatcher.skipClassLoader(matcherProvider).matches(emptyLoader)
3738
}
3839

3940
def "does not skip bootstrap classloader"() {
4041
expect:
41-
!GlobalClassloaderIgnoresMatcher.skipClassLoader().matches(null)
42+
!GlobalClassloaderIgnoresMatcher.skipClassLoader(matcherProvider).matches(null)
43+
}
44+
45+
def "skip bootstrap classloader"() {
46+
IgnoreMatcherProvider skipBootstrapClMatcherProvider = [classloader: {cl -> cl == null ? IgnoreMatcherProvider.Result.IGNORE : IgnoreMatcherProvider.Result.DEFAULT}] as IgnoreMatcherProvider
47+
expect:
48+
GlobalClassloaderIgnoresMatcher.skipClassLoader(skipBootstrapClMatcherProvider).matches(null)
4249
}
4350

4451
def "AgentClassLoader class name is hardcoded in ClassLoaderMatcher"() {

0 commit comments

Comments
 (0)