启动 javaagent
OpenTelemetry javaagent 入口位于OpenTelemetryAgent类。premain和agentmain方法皆委托调用startAgent方法,startAgent方法主要逻辑为引导 opentelemetry-javaagent jar 文件并且委托AgentInitializer完成 agent 的初始化工作。
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 {
// ① 引导 bootstrap jar
File javaagentFile = installBootstrapJar(inst);
InstrumentationHolder.setInstrumentation(inst);
JavaagentFileHolder.setJavaagentFile(javaagentFile);
// ② 初始化 agent
AgentInitializer.initialize(inst, javaagentFile, fromPremain);
} catch (Throwable ex) {
// ignore code
}
}
}
引导 javaagent jar 文件
installBootstrapJar方法主要逻辑为:
- 通过
io.opentelemetry.javaagent.OpenTelemetryAgent定位 opentelemetry-javaagent JAR 文件。 - 通过
java.lang.instrument.Instrumentation的appendToBootstrapClassLoaderSearch方法让 bootstrap class loader 能够加载 opentelemetry-javaagent JAR 中的类。
private static synchronized File installBootstrapJar(Instrumentation inst)
throws IOException, URISyntaxException {
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("!/");
String agentPath = resourcePath.substring(protocolSeparatorIndex + 1, resourceSeparatorIndex);
File javaagentFile = new File(agentPath);
// ① opentelemetry-javaagent jar
JarFile agentJar = new JarFile(javaagentFile, false);
verifyJarManifestMainClassIsThis(javaagentFile, agentJar);
// ② 让 bootstrap CL 能够加载 opentelemetry-javaagent jar 中的 class
inst.appendToBootstrapClassLoaderSearch(agentJar);
return javaagentFile;
}
初始化 agent
Agent 初始化工作代码位于AgentInitializer的initialize方法主要逻辑为:
- 创建
AgentClassLoader对象。 - 创建
AgentStarter执行start方法。
public static void initialize(Instrumentation inst, File javaagentFile,
boolean fromPremain) throws Exception {
isSecurityManagerSupportEnabled = isSecurityManagerSupportEnabled();
execute(
new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
// ① 创建 AgentClassLoader
agentClassLoader = createAgentClassLoader("inst", javaagentFile);
// ② 创建 AgentStarter
agentStarter = createAgentStarter(agentClassLoader, inst, javaagentFile);
// ③ 执行 start 方法,启动 agent
if (!fromPremain || !delayAgentStart()) {
agentStarter.start();
}
return null;
}
});
}
AgentClassLoader
创建 AgentClassLoader
private static ClassLoader createAgentClassLoader(String innerJarFilename, File javaagentFile) {
// innerJarFilename = "inst"
return new AgentClassLoader(javaagentFile, innerJarFilename, isSecurityManagerSupportEnabled);
}
加载 class
- 根据 class 名称,尝试去
inst目录下查找到应的 class,存在则加载。 - class 不在
inst目录下,则委托父 class loader 加载。
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// ...
synchronized (getClassLoadingLock(name)) {
Class<?> clazz = findLoadedClass(name);
// ① first search agent classes
if (clazz == null) {
clazz = findAgentClass(name);
}
// ② search from parent and urls added to this loader
if (clazz == null) {
clazz = super.loadClass(name, false);
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
}
查找 class
- 根据 class 名称转换成对应的文件路径,为路径拼接上前缀
inst/和后缀.classdata。 - 前缀来自成员变量
jarEntryPrefix,在构造器内完成赋值,由AgentInitializer的createAgentClassLoader方法传入字符中"inst",在构造器内拼接上"/",变成"inst/"。 - 后缀为字符串
".class"拼接上getClassSuffix方法返回的固定值"data"而来,即".classdata"。
假设要加载的 class 为okhttp3.OkHttpClient,则最终在 opentelemetry-javaagent JAR 目录下查找的文件路径为inst/okhttp3/OkHttpClient.classdata。
private Class<?> findAgentClass(String name) throws ClassNotFoundException {
// ① 查找对应的 class 文件
JarEntry jarEntry = findJarEntry(name.replace('.', '/') + ".class");
if (jarEntry != null) {
byte[] bytes;
try {
bytes = getJarEntryBytes(jarEntry);
} catch (IOException exception) {
throw new ClassNotFoundException(name, exception);
}
definePackageIfNeeded(name);
return defineClass(name, bytes);
}
return null;
}
private JarEntry findJarEntry(String name) {
// shading renames .class to .classdata
boolean isClass = name.endsWith(".class");
if (isClass) {
// ② 为 .class 文件拼接后缀 "data"
name += getClassSuffix();
}
// ③ 拼接后缀 "inst/"
JarEntry jarEntry = jarFile.getJarEntry(jarEntryPrefix + name);
if (MULTI_RELEASE_JAR_ENABLE) {
jarEntry = findVersionedJarEntry(jarEntry, name);
}
return jarEntry;
}
protected String getClassSuffix() {
return "data";
}
inst目录主要包含 javaagent instrumentation 模块,针对各框架及中间件的支持,展开目录inst/io/opentelemetry/javaagent/instrumentation,可看到 OpenTelemetry javaagent 支持的框架和中间件非常丰富,基本涵盖了常用的开源框架和中间件。由于文件目录过多,为了减少篇幅占用,以下示意图为执行 bash tree -L 1命令后仅保持了部分目录名称后的样子,实际上 OpenTelemetry javaagent 支持的框架和中间件远远多于以下示意图。
...
├── apachedubbo
├── apachehttpclient
├── cassandra
├── elasticsearch
├── executors
├── graphql
├── grizzly
├── grpc
├── hikaricp
├── httpclient
├── hystrix
├── jdbc
├── jedis
├── jetty
├── jms
├── kafkaclients
├── kafkastreams
├── kubernetesclient
├── lettuce
├── micrometer
├── mongo
├── netty
├── okhttp
├── opensearch
├── rabbitmq
├── reactor
├── reactornetty
├── rmi
├── rocketmqclient
├── rxjava
├── servlet
├── sparkjava
├── spring
├── springweb
├── tomcat
├── undertow
├── vertx
└── ...
ExtensionClassLoader
ExtensionClassLoader用于加载 otel instrumentation javaagent 扩展包内的类和资源文件。
创建 ExtensionClassLoader
通过AgentInitializer的createAgentStarter方法创建AgentStarter,调用其start方法执行agent 字节码增强。
创建AgentStarterImpl,代码如下:
private static AgentStarter createAgentStarter(
ClassLoader agentClassLoader, Instrumentation instrumentation, File javaagentFile)
throws Exception {
Class<?> starterClass =
agentClassLoader.loadClass("io.opentelemetry.javaagent.tooling.AgentStarterImpl");
Constructor<?> constructor =
starterClass.getDeclaredConstructor(Instrumentation.class, File.class, boolean.class);
return (AgentStarter)
constructor.newInstance(instrumentation, javaagentFile, isSecurityManagerSupportEnabled);
}
AgentStarterImpl的start方法主要逻辑为:
- 创建 ExtensionClassLoader。
- 通过 Bytebuddy 执行 agent 字节码增强。
@Override
public void start() {
// ① 创建 ExtensionClassLoader
extensionClassLoader = createExtensionClassLoader(getClass().getClassLoader());
// ② 安装 Bytebuddy agent
AgentInstaller.installBytebuddyAgent(instrumentation, extensionClassLoader);
WeakConcurrentMapCleaner.start();
}
创建 Extension ClassLoader,代码位于类ExtensionClassLoader中的getInstance方法,它会为每一个扩展的 otel instrumentation javaagent JAR 创建一个单独的ExtensionClassLoader对象。这些扩展可能包括 SDK 组件(exporters或propagators)和其他工具,它们必须被隔离和着色以减少对用户应用程序的干扰并使其与 otel javaagent 使用的 SDK 兼容。 因此,每个扩展 JAR 都有一个单独的类加载器 并最终将这些类加载器对象通过组合模式封装在MultipleParentClassLoader中,该类由 Bytebuddy 提供,其loadClass类加载方法会委托给通过构造器函数传入的类加载器列表来依次执行,相关代码请见下文。Extension ClassLoader 主要包含三类:
- opentelemetry javaagent JAR 文件中内嵌的扩展 javaagent JAR。
- 通过系统变量
otel.javaagent.extensions或环境变量OTEL_JAVAAGENT_EXTENSIONS指定的扩展 JAR 列表。 - 实验性的扩展 jar,可通过系统变量
otel.javaagent.experimental.extensions或环境变量OTEL_JAVAAGENT_EXPERIMENTAL_EXTENSIONS指定。
public static ClassLoader getInstance(
ClassLoader parent, File javaagentFile, boolean isSecurityManagerSupportEnabled) {
List<URL> extensions = new ArrayList<>();
// ① opentelemetry javaagent JAR 文件中内嵌的扩展 javaagent JAR
includeEmbeddedExtensionsIfFound(parent, extensions, javaagentFile);
// ② opentelemetry 扩展类 javaagent JAR
extensions.addAll(
parseLocation(
System.getProperty(EXTENSIONS_CONFIG, System.getenv("OTEL_JAVAAGENT_EXTENSIONS")),
javaagentFile));
// ③ 实验性的扩展类 javaagent JAR
extensions.addAll(
parseLocation(
System.getProperty(
"otel.javaagent.experimental.extensions",
System.getenv("OTEL_JAVAAGENT_EXPERIMENTAL_EXTENSIONS")),
javaagentFile));
if (extensions.isEmpty()) {
return parent;
}
List<ClassLoader> delegates = new ArrayList<>(extensions.size());
for (URL url : extensions) {
// ④ 分别创建专用的 ExtensionClassLoader
delegates.add(getDelegate(parent, url, isSecurityManagerSupportEnabled));
}
return new MultipleParentClassLoader(parent, delegates);
}
private static URLClassLoader getDelegate(ClassLoader parent, URL extensionUrl,
boolean isSecurityManagerSupportEnabled) {
return new ExtensionClassLoader(extensionUrl, parent, isSecurityManagerSupportEnabled);
}
ExtensionClassLoader派生自URLClassLoader,此类加载器用于从引用的 JAR 文件和目录的 URL 的搜索路径加载类和资源,此类加载器支持从给定 URL 引用的多版本 JAR 文件的内容加载类和资源。
public class ExtensionClassLoader extends URLClassLoader {
private ExtensionClassLoader(
URL url, ClassLoader parent, boolean isSecurityManagerSupportEnabled) {
super(new URL[] {url}, parent);
this.isSecurityManagerSupportEnabled = isSecurityManagerSupportEnabled;
}
}
内嵌扩展 JAR
在 opentelemetry javaagent JAR 的extensions/目录下查找扩展 JAR 文件,将它们复制到临时目录,为复制后的文件映射成URL对象,用于后续创建专用的ExtensionClassLoader。
private static void includeEmbeddedExtensionsIfFound(
ClassLoader parent, List<URL> extensions, File javaagentFile) {
try {
JarFile jarFile = new JarFile(javaagentFile, false);
Enumeration<JarEntry> entryEnumeration = jarFile.entries();
String prefix = "extensions/";
File tempDirectory = null;
while (entryEnumeration.hasMoreElements()) {
JarEntry jarEntry = entryEnumeration.nextElement();
String name = jarEntry.getName();
// ① 查找 opentelemetry javaagent JAR extensions/ 目录下的文件
if (name.startsWith(prefix) && !jarEntry.isDirectory()) {
tempDirectory = ensureTempDirectoryExists(tempDirectory);
File tempFile = new File(tempDirectory, name.substring(prefix.length()));
if (!tempFile
.getCanonicalFile()
.toPath()
.startsWith(tempDirectory.getCanonicalFile().toPath())) {
throw new IllegalStateException("Invalid extension " + name);
}
if (tempFile.createNewFile()) {
tempFile.deleteOnExit();
// ② 将找到的文件拷贝至临时目录下
extractFile(jarFile, jarEntry, tempFile);
// ③ 将拷贝后的文件对象映射为 URL 对象添加到扩展列表,
// 用于后续创建单独的 ExtensionClassLoader
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());
}
}
MultipleParentClassLoader
MultipleParentClassLoader聚合了所有的ExtensionClassLoader对象,其类加载逻辑为依次委托给各Extension Class Loader 对象来完成,直至完成为止;若这些 Extension Class Loader 无法完成类的加载工作,则最终委托给 Bootstrap Class Loader。
MultipleParentClassLoader 的类加载逻辑如下:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// ① 在 opentelemetry javaagent 中,
// 这里的 parents 指上文代码中传入 ExtensionClassLoader 列表
for (ClassLoader parent : parents) {
try {
Class<?> type = parent.loadClass(name);
if (resolve) {
resolveClass(type);
}
return type;
} catch (ClassNotFoundException ignored) {
/* try next class loader */
}
}
return super.loadClass(name, resolve);
}
类加载器层级
OpenTelemetry javaagent 中的类层级关系如下图所示[1](图片来自官方项目)。
参考资料
[1] OpenTelemetry Instrumentation for Java, github.com/open-teleme…