Skip to content

Commit

Permalink
Support for multiple extension jars by scanning the given folder (#3226)
Browse files Browse the repository at this point in the history
  • Loading branch information
iNikem authored Jun 11, 2021
1 parent 31d3f2f commit aa4f07d
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 50 deletions.
1 change: 1 addition & 0 deletions examples/extension/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ ext {
}

repositories {
mavenLocal()
mavenCentral()
maven {
url = uri("https://oss.sonatype.org/content/repositories/snapshots")
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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")))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ static void setupSpec() {

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))
Expand All @@ -93,7 +93,7 @@ void startTarget(int jdk) {
.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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,25 @@ protected String getTargetImage(int jdk) {
}

@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();

Expand All @@ -45,7 +62,5 @@ public void springBootSmokeTestOnJDK() throws IOException, InterruptedException
Assertions.assertNotEquals(0,
countResourcesByValue(traces, "telemetry.auto.version", currentAgentVersion));
Assertions.assertNotEquals(0, countResourcesByValue(traces, "custom.resource", "demo"));

stopTarget();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,69 +9,84 @@
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 {
// NOTE it's important not to use slf4j in this class, because this class is used before slf4j is
// 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
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;
Expand All @@ -20,10 +19,10 @@
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);
}
}
Expand Down

0 comments on commit aa4f07d

Please sign in to comment.