
io.opentelemetry.javaagent.tooling.ExtensionClassLoader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of opentelemetry-javaagent-tooling Show documentation
Show all versions of opentelemetry-javaagent-tooling Show documentation
Instrumentation of Java libraries using OpenTelemetry.
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling;
import io.opentelemetry.context.Context;
import io.opentelemetry.javaagent.tooling.config.EarlyInitAgentConfig;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Files;
import java.security.AllPermission;
import java.security.CodeSource;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.annotation.Nullable;
import net.bytebuddy.dynamic.loading.MultipleParentClassLoader;
/**
* This class creates a class loader 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 class loader and all of them are aggregated with the help of {@link
* MultipleParentClassLoader}.
*/
// TODO find a way to initialize logging before using this class
@SuppressWarnings("SystemOut")
public class ExtensionClassLoader extends URLClassLoader {
public static final String EXTENSIONS_CONFIG = "otel.javaagent.extensions";
private final boolean isSecurityManagerSupportEnabled;
// NOTE it's important not to use logging in this class, because this class is used before logging
// is initialized
static {
ClassLoader.registerAsParallelCapable();
}
public static ClassLoader getInstance(
ClassLoader parent,
File javaagentFile,
boolean isSecurityManagerSupportEnabled,
EarlyInitAgentConfig earlyConfig) {
List extensions = new ArrayList<>();
includeEmbeddedExtensionsIfFound(extensions, javaagentFile);
extensions.addAll(parseLocation(earlyConfig.getString(EXTENSIONS_CONFIG), javaagentFile));
// TODO when logging is configured add warning about deprecated property
if (extensions.isEmpty()) {
return parent;
}
List delegates = new ArrayList<>(extensions.size());
for (URL url : extensions) {
delegates.add(getDelegate(parent, url, isSecurityManagerSupportEnabled));
}
return new MultipleParentClassLoader(parent, delegates);
}
private static void includeEmbeddedExtensionsIfFound(List extensions, File javaagentFile) {
try {
JarFile jarFile = new JarFile(javaagentFile, false);
Enumeration entryEnumeration = jarFile.entries();
String prefix = "extensions/";
File tempDirectory = null;
while (entryEnumeration.hasMoreElements()) {
JarEntry jarEntry = entryEnumeration.nextElement();
String name = jarEntry.getName();
if (name.startsWith(prefix) && !jarEntry.isDirectory()) {
tempDirectory = ensureTempDirectoryExists(tempDirectory);
File tempFile = new File(tempDirectory, name.substring(prefix.length()));
// reject extensions that would be extracted outside of temp directory
// https://security.snyk.io/research/zip-slip-vulnerability
if (!tempFile
.getCanonicalFile()
.toPath()
.startsWith(tempDirectory.getCanonicalFile().toPath())) {
throw new IllegalStateException("Invalid extension " + name);
}
if (tempFile.createNewFile()) {
tempFile.deleteOnExit();
extractFile(jarFile, jarEntry, tempFile);
addFileUrl(extensions, tempFile);
} else {
System.err.println("Failed to create temp file " + tempFile);
}
}
}
} catch (IOException ex) {
System.err.println("Failed to open embedded extensions " + ex.getMessage());
}
}
private static File ensureTempDirectoryExists(File tempDirectory) throws IOException {
if (tempDirectory == null) {
tempDirectory = Files.createTempDirectory("otel-extensions").toFile();
tempDirectory.deleteOnExit();
}
return tempDirectory;
}
private static URLClassLoader getDelegate(
ClassLoader parent, URL extensionUrl, boolean isSecurityManagerSupportEnabled) {
return new ExtensionClassLoader(extensionUrl, parent, isSecurityManagerSupportEnabled);
}
// visible for testing
static List parseLocation(@Nullable String locationName, File javaagentFile) {
if (locationName == null) {
return Collections.emptyList();
}
List result = new ArrayList<>();
for (String location : locationName.split(",")) {
parseLocation(location, javaagentFile, 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(locations, location);
} else if (location.isDirectory()) {
File[] files = location.listFiles(ExtensionClassLoader::isJar);
if (files != null) {
for (File file : files) {
if (isJar(file) && !file.getAbsolutePath().equals(javaagentFile.getAbsolutePath())) {
addFileUrl(locations, file);
}
}
}
}
}
private static boolean isJar(File f) {
return f.isFile() && f.getName().endsWith(".jar");
}
private static void addFileUrl(List result, File file) {
try {
// skip shading extension classes if opentelemetry-api is not shaded (happens when using
// disableShadowRelocate=true)
if (Context.class.getName().contains(".shaded.")) {
URL wrappedUrl = new URL("otel", null, -1, "/", new RemappingUrlStreamHandler(file));
result.add(wrappedUrl);
} else {
result.add(file.toURI().toURL());
}
} catch (MalformedURLException ignored) {
System.err.println("Ignoring " + file);
}
}
private static void extractFile(JarFile jarFile, JarEntry jarEntry, File outputFile)
throws IOException {
try (InputStream in = jarFile.getInputStream(jarEntry);
ReadableByteChannel rbc = Channels.newChannel(in);
FileOutputStream fos = new FileOutputStream(outputFile)) {
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
}
}
@Override
protected PermissionCollection getPermissions(CodeSource codesource) {
if (isSecurityManagerSupportEnabled) {
Permissions permissions = new Permissions();
permissions.add(new AllPermission());
return permissions;
}
return super.getPermissions(codesource);
}
private ExtensionClassLoader(
URL url, ClassLoader parent, boolean isSecurityManagerSupportEnabled) {
super(new URL[] {url}, parent);
this.isSecurityManagerSupportEnabled = isSecurityManagerSupportEnabled;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy