Skip to content

Commit 222a95d

Browse files
committed
Support mapping extensions from different providers into a form that can be used by the Datadog tracer.
1 parent 97c73dd commit 222a95d

File tree

8 files changed

+402
-137
lines changed

8 files changed

+402
-137
lines changed

dd-java-agent/agent-builder/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package datadog.trace.agent.tooling;
22

3+
import static datadog.trace.agent.tooling.ExtensionFinder.findExtensions;
4+
import static datadog.trace.agent.tooling.ExtensionLoader.loadExtensions;
35
import static datadog.trace.agent.tooling.bytebuddy.matcher.GlobalIgnoresMatcher.globalIgnoresMatcher;
46
import static net.bytebuddy.matcher.ElementMatchers.isDefaultFinalizer;
57

@@ -242,19 +244,18 @@ public void applied(Iterable<String> instrumentationNames) {
242244
/** Returns an iterable that combines the original sequence with any discovered extensions. */
243245
private static Iterable<InstrumenterModule> withExtensions(Iterable<InstrumenterModule> initial) {
244246
String extensionsPath = InstrumenterConfig.get().getTraceExtensionsPath();
245-
if (null == extensionsPath) {
246-
return initial;
247-
}
248-
final List<InstrumenterModule> extensions = new ExtensionsLoader(extensionsPath).loadModules();
249-
if (extensions.isEmpty()) {
250-
return initial;
251-
}
252-
return new Iterable<InstrumenterModule>() {
253-
@Override
254-
public Iterator<InstrumenterModule> iterator() {
255-
return withExtensions(initial.iterator(), extensions);
247+
if (null != extensionsPath) {
248+
if (findExtensions(extensionsPath, InstrumenterModule.class)) {
249+
final List<InstrumenterModule> extensions = loadExtensions(InstrumenterModule.class);
250+
return new Iterable<InstrumenterModule>() {
251+
@Override
252+
public Iterator<InstrumenterModule> iterator() {
253+
return withExtensions(initial.iterator(), extensions);
254+
}
255+
};
256256
}
257-
};
257+
}
258+
return initial;
258259
}
259260

260261
/** Returns an iterator that combines the original sequence with any discovered extensions. */

dd-java-agent/agent-builder/src/main/java/datadog/trace/agent/tooling/CombiningTransformerBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ public void applyAdvice(ElementMatcher<? super MethodDescription> matcher, Strin
247247
}
248248
advice.add(
249249
new AgentBuilder.Transformer.ForAdvice(customMapping)
250-
.include(Utils.getBootstrapProxy(), Utils.getAgentClassLoader())
250+
.include(Utils.getBootstrapProxy(), Utils.getExtendedClassLoader())
251251
.withExceptionHandler(ExceptionHandlers.defaultExceptionHandler())
252252
.advice(not(ignoredMethods).and(matcher), adviceClass));
253253
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package datadog.trace.agent.tooling;
2+
3+
import static datadog.trace.agent.tooling.ExtensionHandler.DATADOG;
4+
5+
import de.thetaphi.forbiddenapis.SuppressForbidden;
6+
import java.io.File;
7+
import java.io.FileNotFoundException;
8+
import java.io.IOException;
9+
import java.net.MalformedURLException;
10+
import java.net.URL;
11+
import java.net.URLClassLoader;
12+
import java.net.URLConnection;
13+
import java.net.URLStreamHandler;
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
import java.util.jar.JarEntry;
17+
import java.util.jar.JarFile;
18+
import net.bytebuddy.dynamic.loading.MultipleParentClassLoader;
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
22+
/** Finds extensions to the Datadog tracer. */
23+
public final class ExtensionFinder {
24+
private static final Logger log = LoggerFactory.getLogger(ExtensionFinder.class);
25+
26+
private static final ExtensionHandler[] handlers = {DATADOG};
27+
28+
/**
29+
* Discovers extensions on the configured path and creates a classloader for each extension.
30+
* Registers the combined classloader with {@link Utils#setExtendedClassLoader(ClassLoader)}.
31+
*
32+
* @return {@code true} if any extensions were found
33+
*/
34+
public static boolean findExtensions(String extensionsPath, Class<?>... extensionTypes) {
35+
List<ClassLoader> classLoaders = new ArrayList<>();
36+
List<JarFile> unusedJars = new ArrayList<>();
37+
38+
ClassLoader parent = Utils.getAgentClassLoader();
39+
String[] descriptors = descriptors(extensionTypes);
40+
41+
for (JarFile jar : findExtensionJars(extensionsPath)) {
42+
URL extensionURL = findExtensionURL(jar, descriptors);
43+
if (null != extensionURL) {
44+
log.debug("Found extension jar {}", jar.getName());
45+
classLoaders.add(new URLClassLoader(new URL[] {extensionURL}, parent));
46+
} else {
47+
unusedJars.add(jar);
48+
}
49+
}
50+
51+
close(unusedJars);
52+
53+
if (classLoaders.size() > 1) {
54+
Utils.setExtendedClassLoader(new MultipleParentClassLoader(classLoaders));
55+
} else if (!classLoaders.isEmpty()) {
56+
Utils.setExtendedClassLoader(classLoaders.get(0));
57+
}
58+
59+
return !classLoaders.isEmpty();
60+
}
61+
62+
/** Closes jar resources from the extension path which did not contain any extensions. */
63+
private static void close(List<JarFile> unusedJars) {
64+
for (JarFile jar : unusedJars) {
65+
try {
66+
jar.close();
67+
} catch (Exception ignore) {
68+
// move onto next jar
69+
}
70+
}
71+
}
72+
73+
/**
74+
* Uses the registered {@link ExtensionHandler}s to find jars containing matching extensions.
75+
* Creates a URL that uses the matched extension handler to access content from the extension.
76+
*/
77+
private static URL findExtensionURL(JarFile jar, String[] descriptors) {
78+
for (ExtensionHandler handler : handlers) {
79+
for (String descriptor : descriptors) {
80+
if (null != handler.mapEntry(jar, descriptor)) {
81+
return buildExtensionURL(jar, handler);
82+
}
83+
}
84+
}
85+
return null;
86+
}
87+
88+
/** Builds a URL that uses an {@link ExtensionHandler} to access the extension. */
89+
private static URL buildExtensionURL(JarFile jar, ExtensionHandler handler) {
90+
try {
91+
return new URL(
92+
"dd-ext",
93+
null,
94+
-1,
95+
"/",
96+
new URLStreamHandler() {
97+
@Override
98+
protected URLConnection openConnection(URL url) throws IOException {
99+
return openExtension(url, jar, handler);
100+
}
101+
});
102+
} catch (MalformedURLException ignore) {
103+
return null;
104+
}
105+
}
106+
107+
/** Uses the given {@link ExtensionHandler} to access content from the extension. */
108+
static URLConnection openExtension(URL url, JarFile jar, ExtensionHandler handler)
109+
throws IOException {
110+
String file = url.getFile();
111+
if (!file.isEmpty() && file.charAt(0) == '/') {
112+
file = file.substring(1);
113+
}
114+
JarEntry jarEntry = handler.mapEntry(jar, file);
115+
if (null != jarEntry) {
116+
return handler.mapContent(url, jar, jarEntry);
117+
} else {
118+
throw new FileNotFoundException("JAR entry " + file + " not found in " + jar.getName());
119+
}
120+
}
121+
122+
@SuppressForbidden // split on single-character uses fast path
123+
private static List<JarFile> findExtensionJars(String extensionsPath) {
124+
List<JarFile> extensionJars = new ArrayList<>();
125+
for (String entry : extensionsPath.split(",")) {
126+
File file = new File(entry);
127+
if (file.isDirectory()) {
128+
visitDirectory(file, extensionJars);
129+
} else if (isJar(file)) {
130+
addExtensionJar(file, extensionJars);
131+
}
132+
}
133+
return extensionJars;
134+
}
135+
136+
private static void visitDirectory(File dir, List<JarFile> extensionJars) {
137+
File[] files = dir.listFiles(ExtensionFinder::isJar);
138+
if (null != files) {
139+
for (File file : files) {
140+
addExtensionJar(file, extensionJars);
141+
}
142+
}
143+
}
144+
145+
private static void addExtensionJar(File file, List<JarFile> extensionJars) {
146+
try {
147+
extensionJars.add(new JarFile(file, false));
148+
} catch (Exception e) {
149+
log.debug("Problem reading extension jar {}", file, e);
150+
}
151+
}
152+
153+
/** The {@code META-INF/service} descriptors to look for. */
154+
private static String[] descriptors(Class<?>[] extensionTypes) {
155+
String[] descriptors = new String[extensionTypes.length];
156+
for (int i = 0; i < extensionTypes.length; i++) {
157+
descriptors[i] = "META-INF/services/" + extensionTypes[i].getName();
158+
}
159+
return descriptors;
160+
}
161+
162+
private static boolean isJar(File file) {
163+
return file.getName().endsWith(".jar") && file.isFile();
164+
}
165+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package datadog.trace.agent.tooling;
2+
3+
import static java.nio.charset.StandardCharsets.UTF_8;
4+
5+
import java.io.BufferedReader;
6+
import java.io.InputStreamReader;
7+
import java.net.URL;
8+
import java.util.ArrayList;
9+
import java.util.Enumeration;
10+
import java.util.LinkedHashSet;
11+
import java.util.List;
12+
import java.util.Set;
13+
import org.slf4j.Logger;
14+
import org.slf4j.LoggerFactory;
15+
16+
/** Loads extensions into the Datadog tracer. */
17+
public final class ExtensionLoader {
18+
private static final Logger log = LoggerFactory.getLogger(ExtensionLoader.class);
19+
20+
private static final String[] NO_EXTENSIONS = {};
21+
22+
/** Loads extensions from the extended classloader built by {@link ExtensionFinder}. */
23+
public static <T> List<T> loadExtensions(Class<T> extensionType) {
24+
return loadExtensions(Utils.getExtendedClassLoader(), extensionType);
25+
}
26+
27+
private static <T> List<T> loadExtensions(ClassLoader classLoader, Class<T> extensionType) {
28+
List<T> extensions = new ArrayList<>();
29+
for (String className : listExtensionNames(classLoader, extensionType)) {
30+
try {
31+
@SuppressWarnings("unchecked")
32+
Class<T> extensionClass = (Class<T>) classLoader.loadClass(className);
33+
extensions.add(extensionClass.getConstructor().newInstance());
34+
log.debug("Loaded extension {}", className);
35+
} catch (Throwable e) {
36+
log.warn("Failed to load extension {}", className, e);
37+
}
38+
}
39+
return extensions;
40+
}
41+
42+
/** Returns the class names listed under the extension's {@code META-INF/services} descriptor. */
43+
private static String[] listExtensionNames(ClassLoader classLoader, Class<?> extensionType) {
44+
try {
45+
Set<String> lines = new LinkedHashSet<>();
46+
Enumeration<URL> urls =
47+
classLoader.getResources("META-INF/services/" + extensionType.getName());
48+
while (urls.hasMoreElements()) {
49+
try (BufferedReader reader =
50+
new BufferedReader(new InputStreamReader(urls.nextElement().openStream(), UTF_8))) {
51+
String line = reader.readLine();
52+
while (line != null) {
53+
lines.add(line);
54+
line = reader.readLine();
55+
}
56+
}
57+
}
58+
return lines.toArray(new String[0]);
59+
} catch (Throwable e) {
60+
log.warn("Problem reading extensions descriptor", e);
61+
return NO_EXTENSIONS;
62+
}
63+
}
64+
}

dd-java-agent/agent-builder/src/main/java/datadog/trace/agent/tooling/ExtensionsLoader.java

Lines changed: 0 additions & 110 deletions
This file was deleted.

0 commit comments

Comments
 (0)