-
Notifications
You must be signed in to change notification settings - Fork 333
Expand file tree
/
Copy pathExtensionFinder.java
More file actions
192 lines (168 loc) · 6.56 KB
/
ExtensionFinder.java
File metadata and controls
192 lines (168 loc) · 6.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package datadog.trace.agent.tooling;
import static datadog.opentelemetry.tooling.OtelExtensionHandler.OPENTELEMETRY;
import static datadog.trace.agent.tooling.ExtensionHandler.DATADOG;
import datadog.trace.api.telemetry.OtelSpiCollector;
import de.thetaphi.forbiddenapis.SuppressForbidden;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import net.bytebuddy.dynamic.loading.MultipleParentClassLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Finds extensions to the Datadog tracer. */
public final class ExtensionFinder {
private static final Logger log = LoggerFactory.getLogger(ExtensionFinder.class);
private static final ExtensionHandler[] handlers = {OPENTELEMETRY, DATADOG};
private static final String EXTENSIONS_PATH_SOURCE = "extensions_path";
private static final String SERVICES_PREFIX = "META-INF/services/";
private static final String OTEL_NAMESPACE = "io.opentelemetry.";
/**
* Discovers extensions on the configured path and creates a classloader for each extension.
* Registers the combined classloader with {@link Utils#setExtendedClassLoader(ClassLoader)}.
*
* @return {@code true} if any extensions were found
*/
public static boolean findExtensions(String extensionsPath, Class<?>... extensionTypes) {
List<ClassLoader> classLoaders = new ArrayList<>();
List<JarFile> unusedJars = new ArrayList<>();
ClassLoader parent = Utils.getAgentClassLoader();
String[] descriptors = descriptors(extensionTypes);
for (JarFile jar : findExtensionJars(extensionsPath)) {
recordOtelSpiTelemetry(jar);
URL extensionURL = findExtensionURL(jar, descriptors);
if (null != extensionURL) {
log.debug("Found extension jar {}", jar.getName());
classLoaders.add(new URLClassLoader(new URL[] {extensionURL}, parent));
} else {
unusedJars.add(jar);
}
}
close(unusedJars);
if (classLoaders.size() > 1) {
Utils.setExtendedClassLoader(new MultipleParentClassLoader(classLoaders));
} else if (!classLoaders.isEmpty()) {
Utils.setExtendedClassLoader(classLoaders.get(0));
}
return !classLoaders.isEmpty();
}
/**
* Reports telemetry for any OpenTelemetry SPI service descriptors present in the jar — any entry
* under {@code META-INF/services/} whose name lives in the {@code io.opentelemetry.*} namespace.
* The jar's existing handle is reused; no new file resources are opened or held.
*/
static void recordOtelSpiTelemetry(JarFile jar) {
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
String name = entries.nextElement().getName();
if (name.startsWith(SERVICES_PREFIX)) {
String fqn = name.substring(SERVICES_PREFIX.length());
if (fqn.startsWith(OTEL_NAMESPACE)) {
OtelSpiCollector.getInstance().recordSpiDetected(fqn, EXTENSIONS_PATH_SOURCE);
}
}
}
}
/** Closes jar resources from the extension path which did not contain any extensions. */
private static void close(List<JarFile> unusedJars) {
for (JarFile jar : unusedJars) {
try {
jar.close();
} catch (Exception ignore) {
// move onto next jar
}
}
}
/**
* Uses the registered {@link ExtensionHandler}s to find jars containing matching extensions.
* Creates a URL that uses the matched extension handler to access content from the extension.
*/
private static URL findExtensionURL(JarFile jar, String[] descriptors) {
for (ExtensionHandler handler : handlers) {
for (String descriptor : descriptors) {
if (null != handler.mapEntry(jar, descriptor)) {
return buildExtensionURL(jar, handler);
}
}
}
return null;
}
/** Builds a URL that uses an {@link ExtensionHandler} to access the extension. */
private static URL buildExtensionURL(JarFile jar, ExtensionHandler handler) {
try {
return new URL("dd-ext", null, -1, "/", new StreamMapper(jar, handler));
} catch (MalformedURLException ignore) {
return null;
}
}
/** Uses a {@link ExtensionHandler} to map and stream content from the extension. */
static final class StreamMapper extends URLStreamHandler {
private final JarFile jar;
private final ExtensionHandler handler;
StreamMapper(JarFile jar, ExtensionHandler handler) {
this.jar = jar;
this.handler = handler;
}
@Override
protected URLConnection openConnection(URL url) throws IOException {
String file = url.getFile();
if (!file.isEmpty() && file.charAt(0) == '/') {
file = file.substring(1);
}
JarEntry jarEntry = handler.mapEntry(jar, file);
if (null != jarEntry) {
return handler.mapContent(url, jar, jarEntry);
} else {
throw new FileNotFoundException("JAR entry " + file + " not found in " + jar.getName());
}
}
}
@SuppressForbidden // split on single-character uses fast path
private static List<JarFile> findExtensionJars(String extensionsPath) {
List<JarFile> extensionJars = new ArrayList<>();
for (String entry : extensionsPath.split(",")) {
File file = new File(entry);
if (file.isDirectory()) {
visitDirectory(file, extensionJars);
} else if (isJar(file)) {
addExtensionJar(file, extensionJars);
}
}
return extensionJars;
}
private static void visitDirectory(File dir, List<JarFile> extensionJars) {
File[] files = dir.listFiles(ExtensionFinder::isJar);
if (null != files) {
for (File file : files) {
addExtensionJar(file, extensionJars);
}
}
}
private static void addExtensionJar(File file, List<JarFile> extensionJars) {
try {
extensionJars.add(new JarFile(file, false));
} catch (Exception e) {
log.debug("Problem reading extension jar {}", file, e);
}
}
/** The {@code META-INF/service} descriptors to look for. */
private static String[] descriptors(Class<?>[] extensionTypes) {
String[] descriptors = new String[extensionTypes.length];
for (int i = 0; i < extensionTypes.length; i++) {
descriptors[i] = "META-INF/services/" + extensionTypes[i].getName();
}
return descriptors;
}
private static boolean isJar(File file) {
return file.getName().endsWith(".jar") && file.isFile();
}
}