diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/AgentClassLoading.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/AgentClassLoading.java
new file mode 100644
index 00000000000..1bdf2ed431d
--- /dev/null
+++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/AgentClassLoading.java
@@ -0,0 +1,34 @@
+package datadog.trace.bootstrap;
+
+/**
+ * Helps distinguish agent class-loading requests from application requests.
+ *
+ *
Should be used in a short try..finally block around the class-loader call.
+ */
+public enum AgentClassLoading {
+ /** Lightweight class-loader probing to select instrumentations. */
+ PROBING_CLASSLOADER,
+ /** Locating class resources for Byte-Buddy. */
+ LOCATING_CLASS,
+ /** Injecting helper classes into class-loaders. */
+ INJECTING_HELPERS;
+
+ private static final ThreadLocal REQUEST = new ThreadLocal<>();
+
+ /**
+ * Gets the current agent class-loading request type; {@code null} if there's no agent request.
+ */
+ public static AgentClassLoading type() {
+ return REQUEST.get();
+ }
+
+ /** Records that this agent class-loading request has begun. */
+ public final void begin() {
+ REQUEST.set(this);
+ }
+
+ /** Records that this agent class-loading request has ended. */
+ public final void end() {
+ REQUEST.remove();
+ }
+}
diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ClassLoaderMatcher.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ClassLoaderMatcher.java
index f49fd15aa8d..59e89692749 100644
--- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ClassLoaderMatcher.java
+++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ClassLoaderMatcher.java
@@ -1,5 +1,7 @@
package datadog.trace.agent.tooling;
+import static datadog.trace.bootstrap.AgentClassLoading.PROBING_CLASSLOADER;
+
import datadog.trace.api.Tracer;
import datadog.trace.bootstrap.PatchLogger;
import datadog.trace.bootstrap.WeakCache;
@@ -140,12 +142,17 @@ private WeakCache getCache() {
}
private boolean hasResources(final ClassLoader cl) {
- for (final String resource : resources) {
- if (cl.getResource(resource) == null) {
- return false;
+ PROBING_CLASSLOADER.begin();
+ try {
+ for (final String resource : resources) {
+ if (cl.getResource(resource) == null) {
+ return false;
+ }
}
+ return true;
+ } finally {
+ PROBING_CLASSLOADER.end();
}
- return true;
}
@Override
diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/HelperInjector.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/HelperInjector.java
index cea41875c1f..89170541021 100644
--- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/HelperInjector.java
+++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/HelperInjector.java
@@ -1,6 +1,7 @@
package datadog.trace.agent.tooling;
import static datadog.trace.agent.tooling.ClassLoaderMatcher.BOOTSTRAP_CLASSLOADER;
+import static datadog.trace.bootstrap.AgentClassLoading.INJECTING_HELPERS;
import datadog.trace.api.Config;
import java.io.File;
@@ -165,6 +166,7 @@ private Map> injectBootstrapClassLoader(
// Failures to create a tempDir are propagated as IOException and handled by transform
final File tempDir = createTempDir();
+ INJECTING_HELPERS.begin();
try {
return ClassInjector.UsingInstrumentation.of(
tempDir,
@@ -172,6 +174,7 @@ private Map> injectBootstrapClassLoader(
AgentInstaller.getInstrumentation())
.injectRaw(classnameToBytes);
} finally {
+ INJECTING_HELPERS.end();
// Delete fails silently
deleteTempDir(tempDir);
}
@@ -179,7 +182,12 @@ private Map> injectBootstrapClassLoader(
private Map> injectClassLoader(
final ClassLoader classLoader, final Map classnameToBytes) {
- return new ClassInjector.UsingReflection(classLoader).injectRaw(classnameToBytes);
+ INJECTING_HELPERS.begin();
+ try {
+ return new ClassInjector.UsingReflection(classLoader).injectRaw(classnameToBytes);
+ } finally {
+ INJECTING_HELPERS.end();
+ }
}
private void ensureModuleCanReadHelperModules(final JavaModule target) {
diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/DDCachingPoolStrategy.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/DDCachingPoolStrategy.java
index 80dfca2ef1a..8c7e1c30ca6 100644
--- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/DDCachingPoolStrategy.java
+++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/DDCachingPoolStrategy.java
@@ -1,5 +1,6 @@
package datadog.trace.agent.tooling.bytebuddy;
+import static datadog.trace.bootstrap.AgentClassLoading.LOCATING_CLASS;
import static net.bytebuddy.agent.builder.AgentBuilder.PoolStrategy;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
@@ -295,6 +296,7 @@ public TypeDescription resolve() {
if (!isResolved) {
Class> klass = null;
ClassLoader classLoader = null;
+ LOCATING_CLASS.begin();
try {
// Please note that by doing a loadClass, the type we are resolving will bypass
// transformation since we are in the middle of a transformation. This should
@@ -310,6 +312,8 @@ public TypeDescription resolve() {
klass = Class.forName(className, false, null);
}
} catch (Throwable ignored) {
+ } finally {
+ LOCATING_CLASS.end();
}
if (klass != null) {
// We managed to load the class
diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/DDClassFileLocator.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/DDClassFileLocator.java
new file mode 100644
index 00000000000..a67ffdfa8e7
--- /dev/null
+++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/DDClassFileLocator.java
@@ -0,0 +1,62 @@
+package datadog.trace.agent.tooling.bytebuddy;
+
+import static datadog.trace.bootstrap.AgentClassLoading.LOCATING_CLASS;
+
+import datadog.trace.agent.tooling.Utils;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.utility.StreamDrainer;
+
+/**
+ * Locate resources with the loading classloader. Because of a quirk with the way classes appended
+ * to the bootstrap classpath work, we first check our bootstrap proxy. If the loading classloader
+ * cannot find the desired resource, check up the classloader hierarchy until a resource is found.
+ */
+public final class DDClassFileLocator extends WeakReference
+ implements ClassFileLocator {
+
+ public DDClassFileLocator(final ClassLoader classLoader) {
+ super(classLoader);
+ }
+
+ @Override
+ public Resolution locate(final String className) throws IOException {
+ String resourceName = className.replace('.', '/') + ".class";
+
+ // try bootstrap first
+ Resolution resolution = loadClassResource(Utils.getBootstrapProxy(), resourceName);
+ ClassLoader cl = get();
+
+ // now go up the classloader hierarchy
+ if (null == resolution && null != cl) {
+ LOCATING_CLASS.begin();
+ try {
+ do {
+ resolution = loadClassResource(cl, resourceName);
+ cl = cl.getParent();
+ } while (null == resolution && null != cl);
+ } finally {
+ LOCATING_CLASS.end();
+ }
+ }
+
+ return resolution != null ? resolution : new Resolution.Illegal(className);
+ }
+
+ @Override
+ public void close() {
+ // nothing to close
+ }
+
+ private static Resolution loadClassResource(
+ final ClassLoader classLoader, final String resourceName) throws IOException {
+ try (InputStream in = classLoader.getResourceAsStream(resourceName)) {
+ if (null != in) {
+ return new Resolution.Explicit(StreamDrainer.DEFAULT.drain(in));
+ }
+ return null;
+ }
+ }
+}
diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/DDLocationStrategy.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/DDLocationStrategy.java
index a3ec740f9d7..ee1eb72251a 100644
--- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/DDLocationStrategy.java
+++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/DDLocationStrategy.java
@@ -1,30 +1,18 @@
package datadog.trace.agent.tooling.bytebuddy;
-import datadog.trace.agent.tooling.Utils;
-import java.util.ArrayList;
-import java.util.List;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.utility.JavaModule;
-/**
- * Locate resources with the loading classloader. Because of a quirk with the way classes appended
- * to the bootstrap classpath work, we first check our bootstrap proxy. If the loading classloader
- * cannot find the desired resource, check up the classloader hierarchy until a resource is found.
- */
-public class DDLocationStrategy implements AgentBuilder.LocationStrategy {
+/** Strategy that uses {@link DDClassFileLocator} to locate class files. */
+public final class DDLocationStrategy implements AgentBuilder.LocationStrategy {
public ClassFileLocator classFileLocator(final ClassLoader classLoader) {
return classFileLocator(classLoader, null);
}
@Override
- public ClassFileLocator classFileLocator(ClassLoader classLoader, final JavaModule javaModule) {
- final List locators = new ArrayList<>();
- locators.add(ClassFileLocator.ForClassLoader.of(Utils.getBootstrapProxy()));
- while (classLoader != null) {
- locators.add(ClassFileLocator.ForClassLoader.WeaklyReferenced.of(classLoader));
- classLoader = classLoader.getParent();
- }
- return new ClassFileLocator.Compound(locators.toArray(new ClassFileLocator[0]));
+ public ClassFileLocator classFileLocator(
+ final ClassLoader classLoader, final JavaModule javaModule) {
+ return new DDClassFileLocator(classLoader);
}
}
diff --git a/dd-java-agent/instrumentation/osgi-4.3/osgi-4.3.gradle b/dd-java-agent/instrumentation/osgi-4.3/osgi-4.3.gradle
new file mode 100644
index 00000000000..bdd779e33e9
--- /dev/null
+++ b/dd-java-agent/instrumentation/osgi-4.3/osgi-4.3.gradle
@@ -0,0 +1,21 @@
+muzzle {
+ // old coordinates
+ pass {
+ group = 'org.osgi'
+ module = 'org.osgi.core'
+ versions = '[4.3,]'
+ }
+
+ // new coordinates
+ pass {
+ group = 'org.osgi'
+ module = 'osgi.core'
+ versions = '[4.3.1,]'
+ }
+}
+
+apply from: "$rootDir/gradle/java.gradle"
+
+dependencies {
+ compileOnly group: 'org.osgi', name: 'org.osgi.core', version: '4.3.0'
+}
diff --git a/dd-java-agent/instrumentation/osgi-4.3/src/main/java/datadog/trace/instrumentation/osgi43/BundleReferenceInstrumentation.java b/dd-java-agent/instrumentation/osgi-4.3/src/main/java/datadog/trace/instrumentation/osgi43/BundleReferenceInstrumentation.java
new file mode 100644
index 00000000000..83e835be544
--- /dev/null
+++ b/dd-java-agent/instrumentation/osgi-4.3/src/main/java/datadog/trace/instrumentation/osgi43/BundleReferenceInstrumentation.java
@@ -0,0 +1,190 @@
+package datadog.trace.instrumentation.osgi43;
+
+import static datadog.trace.agent.tooling.ClassLoaderMatcher.hasClassesNamed;
+import static datadog.trace.agent.tooling.bytebuddy.matcher.DDElementMatchers.extendsClass;
+import static datadog.trace.agent.tooling.bytebuddy.matcher.DDElementMatchers.implementsInterface;
+import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
+import static datadog.trace.bootstrap.AgentClassLoading.PROBING_CLASSLOADER;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+
+import com.google.auto.service.AutoService;
+import datadog.trace.agent.tooling.Instrumenter;
+import datadog.trace.bootstrap.AgentClassLoading;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.osgi.framework.BundleReference;
+
+@AutoService(Instrumenter.class)
+public final class BundleReferenceInstrumentation extends Instrumenter.Tracing {
+ public BundleReferenceInstrumentation() {
+ super("classloading", "osgi");
+ }
+
+ @Override
+ public ElementMatcher classLoaderMatcher() {
+ return hasClassesNamed("org.osgi.framework.wiring.BundleWiring");
+ }
+
+ @Override
+ public ElementMatcher typeMatcher() {
+ return extendsClass(named("java.lang.ClassLoader"))
+ .and(implementsInterface(named("org.osgi.framework.BundleReference")));
+ }
+
+ @Override
+ public String[] helperClassNames() {
+ return new String[] {
+ packageName + ".BundleWiringHelper",
+ packageName + ".BundleWiringHelper$1",
+ packageName + ".BundleWiringHelper$2"
+ };
+ }
+
+ @Override
+ public Map extends ElementMatcher super MethodDescription>, String> transformers() {
+ final Map, String> transformers = new HashMap<>();
+ transformers.put(
+ isMethod()
+ .and(named("getResource"))
+ .and(takesArguments(1).and(takesArgument(0, String.class))),
+ BundleReferenceInstrumentation.class.getName() + "$WidenGetResourceAdvice");
+ transformers.put(
+ isMethod()
+ .and(named("getResourceAsStream"))
+ .and(takesArguments(1).and(takesArgument(0, String.class))),
+ BundleReferenceInstrumentation.class.getName() + "$WidenGetResourceAsStreamAdvice");
+ transformers.put(
+ isMethod()
+ .and(named("loadClass"))
+ .and(
+ takesArguments(1)
+ .and(takesArgument(0, String.class))
+ .or(
+ takesArguments(2)
+ .and(takesArgument(0, String.class))
+ .and(takesArgument(1, boolean.class)))),
+ BundleReferenceInstrumentation.class.getName() + "$WidenLoadClassAdvice");
+ return transformers;
+ }
+
+ /**
+ * Bypass local visibility rules by repeating failed requests from bundles wired as dependencies.
+ * Also supports light probing of class-loaders without triggering further resolution of bundles.
+ *
+ * We only do this for agent requests that require this additional visibility.
+ */
+ public static class WidenGetResourceAdvice {
+ @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, suppress = Throwable.class)
+ public static Object onEnter(
+ @Advice.This final BundleReference thiz, @Advice.Argument(0) final String name) {
+ AgentClassLoading requestType = AgentClassLoading.type();
+ // avoid probing "java/..." class resources, use standard lookup for them
+ if (PROBING_CLASSLOADER == requestType && !name.startsWith("java/")) {
+ return BundleWiringHelper.probeResource(thiz.getBundle(), name);
+ }
+ return null;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void onExit(
+ @Advice.This final BundleReference thiz,
+ @Advice.Argument(0) final String name,
+ @Advice.Return(readOnly = false) URL result,
+ @Advice.Thrown(readOnly = false) Throwable error,
+ @Advice.Enter final Object resultFromProbe) {
+ if (null != resultFromProbe) {
+ if (resultFromProbe instanceof URL) {
+ result = (URL) resultFromProbe;
+ } else {
+ // probe returned SKIP_REQUEST
+ }
+ } else if (null == result) {
+ AgentClassLoading requestType = AgentClassLoading.type();
+ if (null != requestType) {
+ requestType.end(); // avoid looping back into our advice
+ try {
+ // widen search by peeking inside bundle wiring
+ result = BundleWiringHelper.getResource(thiz.getBundle(), name);
+ if (null != result) {
+ error = null; // clear any error from original call
+ }
+ } finally {
+ requestType.begin();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Bypass local visibility rules by repeating failed requests from bundles wired as dependencies.
+ *
+ *
We only do this for agent requests that require this additional visibility.
+ */
+ public static class WidenGetResourceAsStreamAdvice {
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void onExit(
+ @Advice.This final BundleReference thiz,
+ @Advice.Argument(0) final String name,
+ @Advice.Return(readOnly = false) InputStream result,
+ @Advice.Thrown(readOnly = false) Throwable error) {
+ if (null == result) {
+ AgentClassLoading requestType = AgentClassLoading.type();
+ if (null != requestType) {
+ requestType.end(); // avoid looping back into our advice
+ try {
+ // widen search by peeking inside bundle wiring
+ URL resource = BundleWiringHelper.getResource(thiz.getBundle(), name);
+ if (null != resource) {
+ result = resource.openStream();
+ error = null; // clear any error from original call
+ }
+ } catch (IOException e) {
+ // ignore missing resource
+ } finally {
+ requestType.begin();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Bypass local visibility rules by repeating failed requests from bundles wired as dependencies.
+ *
+ *
We only do this for agent requests that require this additional visibility.
+ */
+ public static class WidenLoadClassAdvice {
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void onExit(
+ @Advice.This final BundleReference thiz,
+ @Advice.Argument(0) final String name,
+ @Advice.Return(readOnly = false) Class> result,
+ @Advice.Thrown(readOnly = false) Throwable error) {
+ if (null == result) {
+ AgentClassLoading requestType = AgentClassLoading.type();
+ if (null != requestType) {
+ requestType.end(); // avoid looping back into our advice
+ try {
+ // widen search by peeking inside bundle wiring
+ result = BundleWiringHelper.loadClass(thiz.getBundle(), name);
+ if (null != result) {
+ error = null; // clear any error from original call
+ }
+ } finally {
+ requestType.begin();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/dd-java-agent/instrumentation/osgi-4.3/src/main/java/datadog/trace/instrumentation/osgi43/BundleWiringHelper.java b/dd-java-agent/instrumentation/osgi-4.3/src/main/java/datadog/trace/instrumentation/osgi43/BundleWiringHelper.java
new file mode 100644
index 00000000000..cd5304c6ce9
--- /dev/null
+++ b/dd-java-agent/instrumentation/osgi-4.3/src/main/java/datadog/trace/instrumentation/osgi43/BundleWiringHelper.java
@@ -0,0 +1,141 @@
+package datadog.trace.instrumentation.osgi43;
+
+import static org.osgi.framework.wiring.BundleRevision.PACKAGE_NAMESPACE;
+
+import datadog.trace.api.Config;
+import datadog.trace.api.Function;
+import java.net.URL;
+import java.util.ArrayDeque;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.BundleWire;
+import org.osgi.framework.wiring.BundleWiring;
+
+/**
+ * {@link BundleWiring} helper that searches a bundle's required wires to see if they have access to
+ * resources and classes that are not visible from the initial bundle. This relaxation of modularity
+ * rules is sometimes necessary when injecting advice, because it may refer to related packages that
+ * would have been imported if the original code had needed them.
+ */
+public final class BundleWiringHelper {
+
+ // how far to take the breadth-first search of required wires (Import-Package, etc.)
+ private static final int MAX_DEPTH = Config.get().getOsgiSearchDepth();
+
+ // placeholder when we want byte-buddy to skip the original call and return null
+ private static final Object SKIP_REQUEST = new Object();
+
+ /** Probes for the named resource without using any class-loader related methods. */
+ public static Object probeResource(final Bundle origin, final String resourceName) {
+ URL resource = origin.getEntry(resourceName);
+ if (null != resource) {
+ return resource;
+ }
+ // not in the bundle, lets check for a direct import of the containing package
+ BundleWiring wiring = (BundleWiring) origin.adapt(BundleWiring.class);
+ List importWires = wiring.getRequiredWires(PACKAGE_NAMESPACE);
+ if (null != importWires) {
+ int lastSlash = resourceName.lastIndexOf('/');
+ if (lastSlash > 0) {
+ String pkg = resourceName.substring(0, lastSlash).replace('/', '.');
+ for (BundleWire wire : importWires) {
+ if (pkg.equals(wire.getCapability().getAttributes().get(PACKAGE_NAMESPACE))) {
+ // class resource comes from a transitive import - to avoid cost of finding it, and
+ // because classloader matching/probing just checks existence, we return a resource
+ // we know exists to stand-in for the real resource
+ return origin.getEntry("META-INF/MANIFEST.MF");
+ }
+ }
+ }
+ }
+ return SKIP_REQUEST;
+ }
+
+ /** Delegates the resource request to any bundles wired as dependencies. */
+ public static URL getResource(final Bundle origin, final String resourceName) {
+ return searchTransitiveWires(
+ origin,
+ new Function() {
+ @Override
+ public URL apply(final BundleWiring wiring) {
+ return wiring.getBundle().getResource(resourceName);
+ }
+ });
+ }
+
+ /** Delegates the class-load request to any bundles wired as dependencies. */
+ public static Class> loadClass(final Bundle origin, final String className) {
+ return searchTransitiveWires(
+ origin,
+ new Function>() {
+ @Override
+ public Class> apply(final BundleWiring wiring) {
+ try {
+ return wiring.getBundle().loadClass(className);
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+ });
+ }
+
+ /** Searches any bundles wired as transitive dependencies using the filter to find a match. */
+ private static T searchTransitiveWires(
+ final Bundle origin, final Function filter) {
+ BundleWiring wiring = (BundleWiring) origin.adapt(BundleWiring.class);
+
+ Queue search = new ArrayDeque<>();
+ Set visited = new HashSet<>();
+
+ search.add(wiring);
+ visited.add(wiring.getRevision()); // avoid dependency cycles
+
+ int countToNextDepth = search.size();
+ int depth = 1;
+
+ // breadth-first search, keep track of how many bundles to search before the next level
+ while (depth <= MAX_DEPTH && !search.isEmpty()) {
+ T result = searchDirectWires(search, filter, depth < MAX_DEPTH, visited);
+ if (null != result) {
+ return result;
+ }
+
+ if (--countToNextDepth == 0) {
+ if (depth < MAX_DEPTH) {
+ countToNextDepth = search.size();
+ }
+ depth++;
+ }
+ }
+
+ return null;
+ }
+
+ /** Searches a bundle's direct dependencies (Import-Package, Require-Bundle etc.) */
+ private static T searchDirectWires(
+ final Queue search,
+ final Function filter,
+ final boolean expandSearch,
+ final Set visited) {
+
+ List wires = search.remove().getRequiredWires(null);
+ if (null != wires) {
+ for (BundleWire wire : wires) {
+ BundleWiring wiring = wire.getProviderWiring();
+ if (null != wiring && visited.add(wiring.getRevision())) {
+ T result = filter.apply(wiring);
+ if (null != result) {
+ return result;
+ } else if (expandSearch) {
+ search.add(wiring); // will be processed at the next level of search
+ }
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/dd-smoke-tests/osgi/src/test/groovy/datadog/smoketest/AbstractOSGiSmokeTest.groovy b/dd-smoke-tests/osgi/src/test/groovy/datadog/smoketest/AbstractOSGiSmokeTest.groovy
index a900b75b3ee..a2abcabb990 100644
--- a/dd-smoke-tests/osgi/src/test/groovy/datadog/smoketest/AbstractOSGiSmokeTest.groovy
+++ b/dd-smoke-tests/osgi/src/test/groovy/datadog/smoketest/AbstractOSGiSmokeTest.groovy
@@ -18,6 +18,7 @@ abstract class AbstractOSGiSmokeTest extends AbstractSmokeTest {
List command = new ArrayList<>()
command.add(javaPath())
command.addAll(defaultJavaProperties)
+ command.add("-Ddd.profiling.enabled=false")
command.add((String) "-Dorg.osgi.framework.storage=${storageDir}")
command.add("-Dorg.osgi.framework.bootdelegation=")
command.add("-Dorg.osgi.framework.bundle.parent=framework")
@@ -39,10 +40,23 @@ abstract class AbstractOSGiSmokeTest extends AbstractSmokeTest {
def "example application runs without errors"() {
when:
testedProcess.waitFor()
- checkLog()
+ boolean instrumentedMessageClient = false
+ checkLog {
+ // check for additional OSGi class-loader issues
+ if (it.contains("Cannot resolve type description") ||
+ it.contains("Instrumentation muzzled")) {
+ println it
+ logHasErrors = true
+ }
+ if (it.contains("Transformed datadog.smoketest.osgi.client.MessageClient")) {
+ println it
+ instrumentedMessageClient = true
+ }
+ }
then:
testedProcess.exitValue() == 0
+ instrumentedMessageClient
!logHasErrors
}
}
diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java
index cc3dc57ebb8..7a107a49198 100644
--- a/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java
+++ b/dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java
@@ -44,6 +44,9 @@ public final class TraceInstrumentationConfig {
"kafka.client.base64.decoding.enabled";
public static final String HYSTRIX_TAGS_ENABLED = "hystrix.tags.enabled";
+
+ public static final String OSGI_SEARCH_DEPTH = "osgi.search.depth";
+
public static final String SERVLET_PRINCIPAL_ENABLED = "trace.servlet.principal.enabled";
public static final String SERVLET_ASYNC_TIMEOUT_ERROR = "trace.servlet.async-timeout.error";
diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java
index d54d220deab..bec12ee723c 100644
--- a/internal-api/src/main/java/datadog/trace/api/Config.java
+++ b/internal-api/src/main/java/datadog/trace/api/Config.java
@@ -70,6 +70,7 @@
import static datadog.trace.api.config.TraceInstrumentationConfig.JDBC_PREPARED_STATEMENT_CLASS_NAME;
import static datadog.trace.api.config.TraceInstrumentationConfig.LEGACY_CONTEXT_FIELD_INJECTION;
import static datadog.trace.api.config.TraceInstrumentationConfig.LOGS_MDC_TAGS_INJECTION_ENABLED;
+import static datadog.trace.api.config.TraceInstrumentationConfig.OSGI_SEARCH_DEPTH;
import static datadog.trace.api.config.TraceInstrumentationConfig.SERIALVERSIONUID_FIELD_INJECTION;
import static datadog.trace.api.config.TracerConfig.ENABLE_TRACE_AGENT_V05;
@@ -400,6 +401,9 @@ private String profilingProxyPasswordMasker() {
@Getter private final boolean kafkaClientBase64DecodingEnabled;
@Getter private final boolean hystrixTagsEnabled;
+
+ @Getter private final int osgiSearchDepth;
+
@Getter private final boolean servletPrincipalEnabled;
@Getter private final boolean servletAsyncTimeoutError;
@@ -761,6 +765,8 @@ private Config(final String runtimeId, final ConfigProvider configProvider) {
hystrixTagsEnabled = configProvider.getBoolean(HYSTRIX_TAGS_ENABLED, false);
+ osgiSearchDepth = configProvider.getInteger(OSGI_SEARCH_DEPTH, 1);
+
servletPrincipalEnabled =
configProvider.getBoolean(TraceInstrumentationConfig.SERVLET_PRINCIPAL_ENABLED, false);
diff --git a/settings.gradle b/settings.gradle
index 39c3fcf70b6..56c219336ef 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -165,6 +165,7 @@ include ':dd-java-agent:instrumentation:opentelemetry'
include ':dd-java-agent:instrumentation:opentracing'
include ':dd-java-agent:instrumentation:opentracing:api-0.31'
include ':dd-java-agent:instrumentation:opentracing:api-0.32'
+include ':dd-java-agent:instrumentation:osgi-4.3'
include ':dd-java-agent:instrumentation:play-2.3'
include ':dd-java-agent:instrumentation:play-2.4'
include ':dd-java-agent:instrumentation:play-2.6'