From 091197a1280be48b200fb2ac46fb31b4e51c30b8 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Wed, 8 Jun 2022 13:34:37 +0300 Subject: [PATCH] Allow specifying a comma separated list of extensions (#6137) * Allow specifying a comma separated list of extensions * update doc * typo * Update examples/extension/README.md Co-authored-by: Trask Stalnaker Co-authored-by: Trask Stalnaker --- examples/extension/README.md | 3 +- .../tooling/ExtensionClassLoader.java | 26 ++++-- .../tooling/ExtensionClassLoaderTest.java | 80 +++++++++++++++++++ 3 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/ExtensionClassLoaderTest.java diff --git a/examples/extension/README.md b/examples/extension/README.md index f88a739144b6..e4c383c9726d 100644 --- a/examples/extension/README.md +++ b/examples/extension/README.md @@ -20,7 +20,8 @@ To add the extension to the instrumentation agent: -Dotel.javaagent.extensions=build/libs/opentelemetry-java-instrumentation-extension-demo-1.0-all.jar -jar myapp.jar ``` -Note: to load multiple extensions, you can specify a directory path for the `otel.javaagent.extensions` value. +Note: to load multiple extensions, you can specify a comma-separated list of extension jars or directories (that +contain extension jars) for the `otel.javaagent.extensions` value. ## Embed extensions in the OpenTelemetry Agent diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ExtensionClassLoader.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ExtensionClassLoader.java index fac4a862a361..b3c7eb548f15 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ExtensionClassLoader.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ExtensionClassLoader.java @@ -16,6 +16,7 @@ import java.nio.channels.ReadableByteChannel; import java.nio.file.Files; import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.jar.JarEntry; @@ -114,27 +115,38 @@ private static URLClassLoader getDelegate(ClassLoader parent, URL extensionUrl) return new ExtensionClassLoader(new URL[] {extensionUrl}, parent); } - private static List parseLocation(String locationName, File javaagentFile) { + // visible for testing + static List parseLocation(String locationName, File javaagentFile) { + if (locationName == null) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); + for (String location : locationName.split(",")) { + parseLocation(location, javaagentFile, result); + } - if (locationName == null) { - return result; + return result; + } + + private static void parseLocation(String locationName, File javaagentFile, List locations) { + if (locationName.isEmpty()) { + return; } File location = new File(locationName); if (isJar(location)) { - addFileUrl(result, location); + addFileUrl(locations, location); } else if (location.isDirectory()) { File[] files = location.listFiles(ExtensionClassLoader::isJar); if (files != null) { for (File file : files) { - if (!file.getAbsolutePath().equals(javaagentFile.getAbsolutePath())) { - addFileUrl(result, file); + if (isJar(file) && !file.getAbsolutePath().equals(javaagentFile.getAbsolutePath())) { + addFileUrl(locations, file); } } } } - return result; } private static boolean isJar(File f) { diff --git a/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/ExtensionClassLoaderTest.java b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/ExtensionClassLoaderTest.java new file mode 100644 index 000000000000..3c65d8836a89 --- /dev/null +++ b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/ExtensionClassLoaderTest.java @@ -0,0 +1,80 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.tooling; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.File; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.jar.Attributes; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class ExtensionClassLoaderTest { + private static final File AGENT_FILE = new File("/agent.jar"); + + @Test + void testParseLocation(@TempDir Path outputDir) throws Exception { + String jarPath1 = createJar("test-1.jar", outputDir); + String jarPath2 = createJar("test-2.jar", outputDir); + // test that non-jar file is skipped + Files.createFile(outputDir.resolve("test.txt")); + { + List result = ExtensionClassLoader.parseLocation(jarPath1, AGENT_FILE); + assertEquals(1, result.size()); + } + { + // empty paths are ignored + List result = ExtensionClassLoader.parseLocation("," + jarPath1 + ",,,", AGENT_FILE); + assertEquals(1, result.size()); + } + { + List result = ExtensionClassLoader.parseLocation(jarPath1 + "," + jarPath2, AGENT_FILE); + assertEquals(2, result.size()); + } + { + List result = ExtensionClassLoader.parseLocation(outputDir.toString(), AGENT_FILE); + assertEquals(2, result.size()); + } + { + List result = + ExtensionClassLoader.parseLocation( + outputDir + "," + jarPath1 + "," + jarPath2, AGENT_FILE); + assertEquals(4, result.size()); + } + { + List result = ExtensionClassLoader.parseLocation("/anydir", AGENT_FILE); + assertEquals(0, result.size()); + } + { + List result = ExtensionClassLoader.parseLocation("/anyfile.jar", AGENT_FILE); + assertEquals(0, result.size()); + } + { + List result = ExtensionClassLoader.parseLocation(jarPath1 + ",/anyfile.jar", AGENT_FILE); + assertEquals(1, result.size()); + } + } + + private static String createJar(String name, Path directory) throws Exception { + Path jarPath = directory.resolve(name); + createJar(jarPath); + return jarPath.toAbsolutePath().toString(); + } + + private static void createJar(Path path) throws Exception { + Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + try (JarOutputStream jar = new JarOutputStream(Files.newOutputStream(path), manifest)) { + // empty jar + } + } +}