Support for multiple extension jars by scanning the given folder (#3226)
This commit is contained in:
parent
31d3f2f8de
commit
aa4f07db6e
|
@ -30,6 +30,7 @@ ext {
|
|||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = uri("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package com.example.javaagent.instrumentation;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
|
||||
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
||||
import io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers;
|
||||
import io.opentelemetry.javaagent.extension.matcher.NameMatchers;
|
||||
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
|
@ -17,13 +18,13 @@ public class DemoServlet3Instrumentation implements TypeInstrumentation {
|
|||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return AgentElementMatchers.safeHasSuperType(
|
||||
NameMatchers.namedOneOf("javax.servlet.Filter", "javax.servlet.http.HttpServlet"));
|
||||
namedOneOf("javax.servlet.Filter", "javax.servlet.http.HttpServlet"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transform(TypeTransformer typeTransformer) {
|
||||
typeTransformer.applyAdviceToMethod(
|
||||
NameMatchers.namedOneOf("doFilter", "service")
|
||||
namedOneOf("doFilter", "service")
|
||||
.and(
|
||||
ElementMatchers.takesArgument(
|
||||
0, ElementMatchers.named("javax.servlet.ServletRequest")))
|
||||
|
|
|
@ -79,9 +79,9 @@ abstract class IntegrationTest {
|
|||
|
||||
protected GenericContainer<?> target;
|
||||
|
||||
void startTarget(int jdk) {
|
||||
void startTarget(String extensionLocation) {
|
||||
target =
|
||||
new GenericContainer<>(getTargetImage(jdk))
|
||||
new GenericContainer<>(getTargetImage(11))
|
||||
.withExposedPorts(8080)
|
||||
.withNetwork(network)
|
||||
.withLogConsumer(new Slf4jLogConsumer(logger))
|
||||
|
@ -93,7 +93,7 @@ abstract class IntegrationTest {
|
|||
.withEnv("JAVA_TOOL_OPTIONS",
|
||||
"-javaagent:/opentelemetry-javaagent.jar -Dotel.javaagent.debug=true")
|
||||
//Asks instrumentation agent to include this extension archive into its runtime
|
||||
.withEnv("OTEL_JAVAAGENT_EXPERIMENTAL_EXTENSIONS", "/opentelemetry-extensions.jar")
|
||||
.withEnv("OTEL_JAVAAGENT_EXPERIMENTAL_EXTENSIONS", extensionLocation)
|
||||
.withEnv("OTEL_BSP_MAX_EXPORT_BATCH", "1")
|
||||
.withEnv("OTEL_BSP_SCHEDULE_DELAY", "10")
|
||||
.withEnv("OTEL_PROPAGATORS", "tracecontext,baggage,demo")
|
||||
|
|
|
@ -19,8 +19,25 @@ class SpringBootIntegrationTest extends IntegrationTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void springBootSmokeTestOnJDK() throws IOException, InterruptedException {
|
||||
startTarget(11);
|
||||
public void extensionsAreLoadedFromJar() throws IOException, InterruptedException {
|
||||
startTarget("/opentelemetry-extensions.jar");
|
||||
|
||||
testAndVerify();
|
||||
|
||||
stopTarget();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extensionsAreLoadedFromFolder() throws IOException, InterruptedException {
|
||||
startTarget("/");
|
||||
|
||||
testAndVerify();
|
||||
|
||||
stopTarget();
|
||||
}
|
||||
|
||||
|
||||
private void testAndVerify() throws IOException, InterruptedException {
|
||||
String url = String.format("http://localhost:%d/greeting", target.getMappedPort(8080));
|
||||
Request request = new Request.Builder().url(url).get().build();
|
||||
|
||||
|
@ -45,7 +62,5 @@ class SpringBootIntegrationTest extends IntegrationTest {
|
|||
Assertions.assertNotEquals(0,
|
||||
countResourcesByValue(traces, "telemetry.auto.version", currentAgentVersion));
|
||||
Assertions.assertNotEquals(0, countResourcesByValue(traces, "custom.resource", "demo"));
|
||||
|
||||
stopTarget();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,15 +9,19 @@ import java.io.File;
|
|||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.bytebuddy.dynamic.loading.MultipleParentClassLoader;
|
||||
|
||||
/**
|
||||
* This classloader is used to load arbitrary extensions for Otel Java instrumentation agent. Such
|
||||
* extensions may include SDK components (exporters or propagators) and additional instrumentations.
|
||||
* They have to be isolated and shaded to reduce interference with the user application and to make
|
||||
* it compatible with shaded SDK used by the agent.
|
||||
* This class creates a classloader which encapsulates arbitrary extensions for Otel Java
|
||||
* instrumentation agent. Such extensions may include SDK components (exporters or propagators) and
|
||||
* additional instrumentations. They have to be isolated and shaded to reduce interference with the
|
||||
* user application and to make it compatible with shaded SDK used by the agent. Thus each extension
|
||||
* jar gets a separate classloader and all of them are aggregated with the help of {@link
|
||||
* MultipleParentClassLoader}.
|
||||
*/
|
||||
// TODO find a way to initialize logging before using this class
|
||||
// TODO support scanning a folder for several extension jars and keep them isolated from each other
|
||||
// Used by AgentInitializer
|
||||
@SuppressWarnings({"unused", "SystemOut"})
|
||||
public class ExtensionClassLoader extends URLClassLoader {
|
||||
|
@ -25,53 +29,64 @@ public class ExtensionClassLoader extends URLClassLoader {
|
|||
// configured, and so using slf4j here would initialize slf4j-simple before we have a chance to
|
||||
// configure the logging levels
|
||||
|
||||
static {
|
||||
ClassLoader.registerAsParallelCapable();
|
||||
}
|
||||
|
||||
public static ClassLoader getInstance(ClassLoader parent) {
|
||||
// TODO add support for old deprecated property otel.javaagent.experimental.exporter.jar
|
||||
URL extension =
|
||||
List<URL> extension =
|
||||
parseLocation(
|
||||
System.getProperty(
|
||||
"otel.javaagent.experimental.extensions",
|
||||
System.getenv("OTEL_JAVAAGENT_EXPERIMENTAL_EXTENSIONS")));
|
||||
|
||||
if (extension == null) {
|
||||
extension =
|
||||
parseLocation(
|
||||
System.getProperty(
|
||||
"otel.javaagent.experimental.initializer.jar",
|
||||
System.getenv("OTEL_JAVAAGENT_EXPERIMENTAL_INITIALIZER_JAR")));
|
||||
// TODO when logging is configured add warning about deprecated property
|
||||
extension.addAll(
|
||||
parseLocation(
|
||||
System.getProperty(
|
||||
"otel.javaagent.experimental.initializer.jar",
|
||||
System.getenv("OTEL_JAVAAGENT_EXPERIMENTAL_INITIALIZER_JAR"))));
|
||||
// TODO when logging is configured add warning about deprecated property
|
||||
|
||||
List<ClassLoader> delegates = new ArrayList<>(extension.size());
|
||||
for (URL url : extension) {
|
||||
delegates.add(getDelegate(parent, url));
|
||||
}
|
||||
return new MultipleParentClassLoader(parent, delegates);
|
||||
}
|
||||
|
||||
private static List<URL> parseLocation(String locationName) {
|
||||
List<URL> result = new ArrayList<>();
|
||||
|
||||
if (locationName == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (extension != null) {
|
||||
try {
|
||||
URL wrappedUrl = new URL("otel", null, -1, "/", new RemappingUrlStreamHandler(extension));
|
||||
return new ExtensionClassLoader(wrappedUrl, parent);
|
||||
} catch (MalformedURLException e) {
|
||||
// This can't happen with current URL constructor
|
||||
throw new IllegalStateException("URL malformed. Unsupported JDK?", e);
|
||||
File location = new File(locationName);
|
||||
if (location.isFile()) {
|
||||
addFileUrl(result, location);
|
||||
} else if (location.isDirectory()) {
|
||||
File[] files = location.listFiles(f -> f.isFile() && f.getName().endsWith(".jar"));
|
||||
if (files != null) {
|
||||
// TODO filter out agent file itself
|
||||
for (File file : files) {
|
||||
addFileUrl(result, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
return parent;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static URL parseLocation(String name) {
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
private static void addFileUrl(List<URL> result, File file) {
|
||||
try {
|
||||
return new File(name).toURI().toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
System.err.printf(
|
||||
"Filename could not be parsed: %s. Extension location is ignored%n", e.getMessage());
|
||||
URL wrappedUrl = new URL("otel", null, -1, "/", new RemappingUrlStreamHandler(file));
|
||||
result.add(wrappedUrl);
|
||||
} catch (MalformedURLException ignored) {
|
||||
System.err.println("Ignoring " + file);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ExtensionClassLoader(URL url, ClassLoader parent) {
|
||||
super(new URL[] {url}, parent);
|
||||
private static URLClassLoader getDelegate(ClassLoader parent, URL extensionUrl) {
|
||||
return new ExtensionClassLoader(new URL[] {extensionUrl}, parent);
|
||||
}
|
||||
|
||||
private ExtensionClassLoader(URL[] urls, ClassLoader parent) {
|
||||
super(urls, parent);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import java.io.File;
|
|||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
|
@ -20,10 +19,10 @@ import java.util.jar.JarFile;
|
|||
class RemappingUrlStreamHandler extends URLStreamHandler {
|
||||
private final JarFile delegateJarFile;
|
||||
|
||||
public RemappingUrlStreamHandler(URL delegateJarFileLocation) {
|
||||
public RemappingUrlStreamHandler(File delegateFile) {
|
||||
try {
|
||||
delegateJarFile = new JarFile(new File(delegateJarFileLocation.toURI()), false);
|
||||
} catch (URISyntaxException | IOException e) {
|
||||
delegateJarFile = new JarFile(delegateFile, false);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Unable to read internal jar", e);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue