Make global ignore matchers configurable
ADD
javaagent-spi/src/main/java/io/opentelemetry/javaagent/spi/IgnoreMatcherProvider.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.spi;
+
+import net.bytebuddy.description.type.TypeDescription;
+
+/**
+ * {@link IgnoreMatcherProvider} can be used to ignore (or allow) types (e.g. classes or
+ * classloaders) from being instrumented. OpenTelemetry agent by default ignores specific set of
+ * classes (e.g. {@code org.gradle.*}) and classloaders. This is mainly done to improve startup
+ * time, but also to explicitly disable instrumentation of a specific types (e.g. other agents). An
+ * implementation of this class can be used to override this behaviour.
+ *
+ * <p>This is a service provider interface that requires implementations to be registered in {@code
+ * META-INF/services} folder. Only a single implementation of this SPI can be provided.
+ */
+public interface IgnoreMatcherProvider {
+
+ /**
+ * Whether to ignore (or allow) type. This method is called for every class, therefore the
+ * implementation has to be as efficient as possible.
+ *
+ * @param target a class.
+ * @return the result of the ignore evaluation.
+ */
+ Result type(TypeDescription target);
+
+ /**
+ * Whether to ignore (or allow) classloader. This method is called for every classloader,
+ * therefore the implementation has to be as efficient as possible.
+ *
+ * @param classLoader a classloader.
+ * @return the result of the ignore evaluation.
+ */
+ Result classloader(ClassLoader classLoader);
+
+ /** Result of the ignore evaluation. */
+ enum Result {
+ /** Default - delegate the evaluation to global ignore matchers from javaagent-tooling. */
+ DEFAULT,
+ /** Ignore instrumentation for a type. */
+ IGNORE,
+ /** Allow instrumentation for a type. */
+ ALLOW
+ }
+}

MODIFY
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java
@@ -20,6 +20,7 @@ import io.opentelemetry.javaagent.instrumentation.api.SafeServiceLoader;
import io.opentelemetry.javaagent.spi.BootstrapPackagesProvider;
import io.opentelemetry.javaagent.spi.ByteBuddyAgentCustomizer;
import io.opentelemetry.javaagent.spi.ComponentInstaller;
+import io.opentelemetry.javaagent.spi.IgnoreMatcherProvider;
import io.opentelemetry.javaagent.tooling.config.ConfigInitializer;
import io.opentelemetry.javaagent.tooling.context.FieldBackedProvider;
import io.opentelemetry.javaagent.tooling.matcher.GlobalClassloaderIgnoresMatcher;
@@ -29,6 +30,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
@@ -97,6 +99,10 @@ public class AgentInstaller {
FieldBackedProvider.resetContextMatchers();
+ IgnoreMatcherProvider ignoreMatcherProvider = loadIgnoreMatcherProvider();
+ log.debug(
+ "Ignore matcher provider {} will be used", ignoreMatcherProvider.getClass().getName());
+
AgentBuilder.Ignored ignoredAgentBuilder =
new AgentBuilder.Default()
.disableClassFormatChanges()
@@ -109,12 +115,13 @@ public class AgentInstaller {
// FIXME: we cannot enable it yet due to BB/JVM bug, see
// https://github.com/raphw/byte-buddy/issues/558
// .with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED)
- .ignore(any(), GlobalClassloaderIgnoresMatcher.skipClassLoader());
+ .ignore(any(), GlobalClassloaderIgnoresMatcher.skipClassLoader(ignoreMatcherProvider));
ignoredAgentBuilder =
ignoredAgentBuilder.or(
globalIgnoresMatcher(
- Config.get().getBooleanProperty(DISABLE_GLOBAL_LIBRARY_IGNORES_FOR_TEST, false)));
+ Config.get().getBooleanProperty(DISABLE_GLOBAL_LIBRARY_IGNORES_FOR_TEST, false),
+ ignoreMatcherProvider));
ignoredAgentBuilder = ignoredAgentBuilder.or(matchesConfiguredExcludes());
@@ -203,6 +210,17 @@ public class AgentInstaller {
return ServiceLoader.load(ComponentInstaller.class, AgentInstaller.class.getClassLoader());
}
+ private static IgnoreMatcherProvider loadIgnoreMatcherProvider() {
+ ServiceLoader<IgnoreMatcherProvider> ignoreMatcherProviders =
+ ServiceLoader.load(IgnoreMatcherProvider.class, AgentInstaller.class.getClassLoader());
+
+ Iterator<IgnoreMatcherProvider> iterator = ignoreMatcherProviders.iterator();
+ if (iterator.hasNext()) {
+ return iterator.next();
+ }
+ return new NoopIgnoreMatcherProvider();
+ }
+
private static Iterable<ByteBuddyAgentCustomizer> loadByteBuddyAgentCustomizers() {
return ServiceLoader.load(
ByteBuddyAgentCustomizer.class, AgentInstaller.class.getClassLoader());
@@ -547,5 +565,17 @@ public class AgentInstaller {
AgentInstaller.class.getName() + " loaded on " + AgentInstaller.class.getClassLoader());
}
+ private static class NoopIgnoreMatcherProvider implements IgnoreMatcherProvider {
+ @Override
+ public Result classloader(ClassLoader classLoader) {
+ return Result.DEFAULT;
+ }
+
+ @Override
+ public Result type(TypeDescription target) {
+ return Result.DEFAULT;
+ }
+ }
+
private AgentInstaller() {}
}

MODIFY
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/matcher/GlobalClassloaderIgnoresMatcher.java
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/matcher/GlobalClassloaderIgnoresMatcher.java
@@ -7,6 +7,7 @@ package io.opentelemetry.javaagent.tooling.matcher;
import io.opentelemetry.javaagent.bootstrap.PatchLogger;
import io.opentelemetry.javaagent.bootstrap.WeakCache;
+import io.opentelemetry.javaagent.spi.IgnoreMatcherProvider;
import io.opentelemetry.javaagent.tooling.AgentTooling;
import io.opentelemetry.javaagent.tooling.bytebuddy.matcher.ClassLoaderMatcher;
import net.bytebuddy.matcher.ElementMatcher;
@@ -17,8 +18,6 @@ public class GlobalClassloaderIgnoresMatcher
extends ElementMatcher.Junction.AbstractBase<ClassLoader> {
private static final Logger log = LoggerFactory.getLogger(GlobalClassloaderIgnoresMatcher.class);
- private static final GlobalClassloaderIgnoresMatcher INSTANCE =
- new GlobalClassloaderIgnoresMatcher();
/* Cache of classloader-instance -> (true|false). True = skip instrumentation. False = safe to instrument. */
private static final String AGENT_CLASSLOADER_NAME =
"io.opentelemetry.javaagent.bootstrap.AgentClassLoader";
@@ -26,14 +25,29 @@ public class GlobalClassloaderIgnoresMatcher
"io.opentelemetry.javaagent.tooling.ExporterClassLoader";
private static final WeakCache<ClassLoader, Boolean> skipCache = AgentTooling.newWeakCache();
- private GlobalClassloaderIgnoresMatcher() {}
+ public static ElementMatcher.Junction.AbstractBase<ClassLoader> skipClassLoader(
+ IgnoreMatcherProvider ignoreMatcherProvider) {
+ return new GlobalClassloaderIgnoresMatcher(ignoreMatcherProvider);
+ }
+
+ private final IgnoreMatcherProvider ignoreMatcherProviders;
- public static ElementMatcher.Junction.AbstractBase<ClassLoader> skipClassLoader() {
- return INSTANCE;
+ private GlobalClassloaderIgnoresMatcher(IgnoreMatcherProvider ignoreMatcherProviders) {
+ this.ignoreMatcherProviders = ignoreMatcherProviders;
}
@Override
public boolean matches(ClassLoader cl) {
+ IgnoreMatcherProvider.Result ignoreResult = ignoreMatcherProviders.classloader(cl);
+ switch (ignoreResult) {
+ case IGNORE:
+ return true;
+ case ALLOW:
+ return false;
+ case DEFAULT:
+ default:
+ }
+
if (cl == ClassLoaderMatcher.BOOTSTRAP_CLASSLOADER) {
// Don't skip bootstrap loader
return false;

MODIFY
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/matcher/GlobalIgnoresMatcher.java
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/matcher/GlobalIgnoresMatcher.java
package io.opentelemetry.javaagent.tooling.matcher;
+import io.opentelemetry.javaagent.spi.IgnoreMatcherProvider;
import java.util.regex.Pattern;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@@ -32,16 +33,19 @@ public class GlobalIgnoresMatcher<T extends TypeDescription>
Pattern.compile("com\\.mchange\\.v2\\.c3p0\\..*Proxy");
public static <T extends TypeDescription> ElementMatcher.Junction<T> globalIgnoresMatcher(
- boolean skipAdditionalLibraryMatcher) {
- return new GlobalIgnoresMatcher<>(skipAdditionalLibraryMatcher);
+ boolean skipAdditionalLibraryMatcher, IgnoreMatcherProvider ignoreMatcherProviders) {
+ return new GlobalIgnoresMatcher<>(skipAdditionalLibraryMatcher, ignoreMatcherProviders);
}
private final ElementMatcher<T> additionalLibraryIgnoreMatcher =
AdditionalLibraryIgnoresMatcher.additionalLibraryIgnoresMatcher();
private final boolean skipAdditionalLibraryMatcher;
+ private final IgnoreMatcherProvider ignoreMatcherProvider;
- private GlobalIgnoresMatcher(boolean skipAdditionalLibraryMatcher) {
+ private GlobalIgnoresMatcher(
+ boolean skipAdditionalLibraryMatcher, IgnoreMatcherProvider ignoreMatcherProvider) {
this.skipAdditionalLibraryMatcher = skipAdditionalLibraryMatcher;
+ this.ignoreMatcherProvider = ignoreMatcherProvider;
}
/**
@@ -51,6 +55,16 @@ public class GlobalIgnoresMatcher<T extends TypeDescription>
*/
@Override
public boolean matches(T target) {
+ IgnoreMatcherProvider.Result ignoreResult = ignoreMatcherProvider.type(target);
+ switch (ignoreResult) {
+ case IGNORE:
+ return true;
+ case ALLOW:
+ return false;
+ case DEFAULT:
+ default:
+ }
+
String name = target.getActualName();
if (name.startsWith("jdk.internal.net.http.")) {

RENAME
javaagent-tooling/src/test/groovy/io/opentelemetry/javaagent/test/ClassLoaderMatcherTest.groovy
javaagent-tooling/src/test/groovy/io/opentelemetry/javaagent/tooling/matcher/ClassLoaderMatcherTest.groovy
* SPDX-License-Identifier: Apache-2.0
*/
-package io.opentelemetry.javaagent.test
+package io.opentelemetry.javaagent.tooling.matcher
import io.opentelemetry.javaagent.bootstrap.AgentClassLoader
-import io.opentelemetry.javaagent.tooling.bytebuddy.matcher.ClassLoaderMatcher
import io.opentelemetry.javaagent.tooling.ExporterClassLoader
-import io.opentelemetry.javaagent.tooling.matcher.GlobalClassloaderIgnoresMatcher
+import io.opentelemetry.javaagent.spi.IgnoreMatcherProvider
import spock.lang.Specification
class ClassLoaderMatcherTest extends Specification {
+ private final IgnoreMatcherProvider matcherProvider = [classloader: {cl -> IgnoreMatcherProvider.Result.DEFAULT}] as IgnoreMatcherProvider
+
def "skips agent classloader"() {
setup:
URL root = new URL("file://")
URLClassLoader agentLoader = new AgentClassLoader(root, null, null)
expect:
- GlobalClassloaderIgnoresMatcher.skipClassLoader().matches(agentLoader)
+ GlobalClassloaderIgnoresMatcher.skipClassLoader(matcherProvider).matches(agentLoader)
}
def "skips exporter classloader"() {
@@ -26,19 +27,25 @@ class ClassLoaderMatcherTest extends Specification {
URL url = new URL("file://")
URLClassLoader exporterLoader = new ExporterClassLoader(url, null)
expect:
- GlobalClassloaderIgnoresMatcher.skipClassLoader().matches(exporterLoader)
+ GlobalClassloaderIgnoresMatcher.skipClassLoader(matcherProvider).matches(exporterLoader)
}
def "does not skip empty classloader"() {
setup:
ClassLoader emptyLoader = new ClassLoader() {}
expect:
- !GlobalClassloaderIgnoresMatcher.skipClassLoader().matches(emptyLoader)
+ !GlobalClassloaderIgnoresMatcher.skipClassLoader(matcherProvider).matches(emptyLoader)
}
def "does not skip bootstrap classloader"() {
expect:
- !GlobalClassloaderIgnoresMatcher.skipClassLoader().matches(null)
+ !GlobalClassloaderIgnoresMatcher.skipClassLoader(matcherProvider).matches(null)
+ }
+
+ def "skip bootstrap classloader"() {
+ IgnoreMatcherProvider skipBootstrapClMatcherProvider = [classloader: {cl -> cl == null ? IgnoreMatcherProvider.Result.IGNORE : IgnoreMatcherProvider.Result.DEFAULT}] as IgnoreMatcherProvider
+ expect:
+ GlobalClassloaderIgnoresMatcher.skipClassLoader(skipBootstrapClMatcherProvider).matches(null)
}
def "AgentClassLoader class name is hardcoded in ClassLoaderMatcher"() {

Make global ignore matchers configurable (#2035)