opentelemetry-java-instrume.../javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/OpenTelemetryAgent.java

138 lines
5.6 KiB
Java

/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent;
import io.opentelemetry.javaagent.bootstrap.AgentInitializer;
import io.opentelemetry.javaagent.bootstrap.InstrumentationHolder;
import io.opentelemetry.javaagent.bootstrap.JavaagentFileHolder;
import java.io.File;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
/**
* Premain-Class for the OpenTelemetry Java agent.
*
* <p>The bootstrap process of the agent is somewhat complicated and care has to be taken to make
* sure things do not get broken by accident.
*
* <p>JVM loads this class onto app's class loader, afterwards agent needs to inject its classes
* onto bootstrap classpath. This leads to this class being visible on bootstrap. This in turn means
* that this class may be loaded again on bootstrap by accident if we ever reference it after
* bootstrap has been setup.
*
* <p>In order to avoid this we need to make sure we do a few things:
*
* <ul>
* <li>Do as little as possible here
* <li>Never reference this class after we have setup bootstrap and jumped over to 'real' agent
* code
* <li>Do not store any static data in this class
* <li>Do dot touch any logging facilities here so we can configure them later
* </ul>
*/
// Too early for logging
@SuppressWarnings("SystemOut")
public final class OpenTelemetryAgent {
public static void premain(String agentArgs, Instrumentation inst) {
startAgent(inst, true);
}
public static void agentmain(String agentArgs, Instrumentation inst) {
startAgent(inst, false);
}
private static void startAgent(Instrumentation inst, boolean fromPremain) {
try {
File javaagentFile = installBootstrapJar(inst);
InstrumentationHolder.setInstrumentation(inst);
JavaagentFileHolder.setJavaagentFile(javaagentFile);
AgentInitializer.initialize(inst, javaagentFile, fromPremain);
} catch (Throwable ex) {
// Don't rethrow. We don't have a log manager here, so just print.
System.err.println("ERROR " + OpenTelemetryAgent.class.getName());
ex.printStackTrace();
}
}
private static synchronized File installBootstrapJar(Instrumentation inst)
throws IOException, URISyntaxException {
// we are not using OpenTelemetryAgent.class.getProtectionDomain().getCodeSource() to get agent
// location because getProtectionDomain does a permission check with security manager
ClassLoader classLoader = OpenTelemetryAgent.class.getClassLoader();
if (classLoader == null) {
classLoader = ClassLoader.getSystemClassLoader();
}
URL url =
classLoader.getResource(OpenTelemetryAgent.class.getName().replace('.', '/') + ".class");
if (url == null || !"jar".equals(url.getProtocol())) {
throw new IllegalStateException("could not get agent jar location from url " + url);
}
String resourcePath = url.toURI().getSchemeSpecificPart();
int protocolSeparatorIndex = resourcePath.indexOf(":");
int resourceSeparatorIndex = resourcePath.indexOf("!/");
if (protocolSeparatorIndex == -1 || resourceSeparatorIndex == -1) {
throw new IllegalStateException("could not get agent location from url " + url);
}
String agentPath = resourcePath.substring(protocolSeparatorIndex + 1, resourceSeparatorIndex);
File javaagentFile = new File(agentPath);
if (!javaagentFile.isFile()) {
throw new IllegalStateException(
"agent jar location doesn't appear to be a file: " + javaagentFile.getAbsolutePath());
}
// verification is very slow before the JIT compiler starts up, which on Java 8 is not until
// after premain execution completes
JarFile agentJar = new JarFile(javaagentFile, false);
verifyJarManifestMainClassIsThis(javaagentFile, agentJar);
inst.appendToBootstrapClassLoaderSearch(agentJar);
return javaagentFile;
}
// this protects against the case where someone adds the contents of opentelemetry-javaagent.jar
// by mistake to their application's "uber.jar"
//
// the reason this can cause issues is because we locate the agent jar based on the CodeSource of
// the OpenTelemetryAgent class, and then we add that jar file to the bootstrap class path
//
// but if we find the OpenTelemetryAgent class in an uber jar file, and we add that (whole) uber
// jar file to the bootstrap class loader, that can cause some applications to break, as there's a
// lot of application and library code that doesn't handle getClassLoader() returning null
// (e.g. https://github.com/qos-ch/logback/pull/291)
private static void verifyJarManifestMainClassIsThis(File jarFile, JarFile agentJar)
throws IOException {
Manifest manifest = agentJar.getManifest();
if (manifest.getMainAttributes().getValue("Premain-Class") == null) {
throw new IllegalStateException(
"The agent was not installed, because the agent was found in '"
+ jarFile
+ "', which doesn't contain a Premain-Class manifest attribute. Make sure that you"
+ " haven't included the agent jar file inside of an application uber jar.");
}
}
/**
* Main entry point.
*
* @param args command line arguments
*/
public static void main(String... args) {
try {
System.out.println(OpenTelemetryAgent.class.getPackage().getImplementationVersion());
} catch (RuntimeException e) {
System.out.println("Failed to parse agent version");
e.printStackTrace();
}
}
private OpenTelemetryAgent() {}
}