Skip to content

Commit 2d4d010

Browse files
authored
Add capability for invokedynamic InstrumentationModules to inject proxies (#9565)
1 parent 3e84ede commit 2d4d010

File tree

16 files changed

+456
-28
lines changed

16 files changed

+456
-28
lines changed

instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/contextdata/v2_17/Log4j2InstrumentationModule.java

+10-3
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@
1414
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
1515
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
1616
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
17+
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
18+
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ClassInjector;
19+
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.InjectionMode;
1720
import java.util.List;
1821
import net.bytebuddy.description.type.TypeDescription;
1922
import net.bytebuddy.matcher.ElementMatcher;
2023

2124
@AutoService(InstrumentationModule.class)
22-
public class Log4j2InstrumentationModule extends InstrumentationModule {
25+
public class Log4j2InstrumentationModule extends InstrumentationModule
26+
implements ExperimentalInstrumentationModule {
2327
public Log4j2InstrumentationModule() {
2428
super("log4j-context-data", "log4j-context-data-2.17");
2529
}
@@ -31,8 +35,11 @@ public void registerHelperResources(HelperResourceBuilder helperResourceBuilder)
3135
}
3236

3337
@Override
34-
public boolean isIndyModule() {
35-
return false;
38+
public void injectClasses(ClassInjector injector) {
39+
injector
40+
.proxyBuilder(
41+
"io.opentelemetry.instrumentation.log4j.contextdata.v2_17.OpenTelemetryContextDataProvider")
42+
.inject(InjectionMode.CLASS_ONLY);
3643
}
3744

3845
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.extension.instrumentation.internal;
7+
8+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
9+
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ClassInjector;
10+
11+
/**
12+
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
13+
* any time.
14+
*/
15+
public interface ExperimentalInstrumentationModule {
16+
17+
/**
18+
* Only functional for Modules where {@link InstrumentationModule#isIndyModule()} returns {@code
19+
* true}.
20+
*
21+
* <p>Normally, helper and advice classes are loaded in a child classloader of the instrumented
22+
* classloader. This method allows to inject classes directly into the instrumented classloader
23+
* instead.
24+
*
25+
* @param injector the builder for injecting classes
26+
*/
27+
default void injectClasses(ClassInjector injector) {}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.extension.instrumentation.internal.injection;
7+
8+
/**
9+
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
10+
* any time.
11+
*/
12+
public interface ClassInjector {
13+
14+
/**
15+
* Create a builder for a proxy class which will be injected into the instrumented {@link
16+
* ClassLoader}. The generated proxy will delegate to the original class, which is loaded in a
17+
* separate classloader.
18+
*
19+
* <p>This removes the need for the proxied class and its dependencies to be visible (just like
20+
* Advices) to the instrumented ClassLoader.
21+
*
22+
* @param classToProxy the fully qualified name of the class for which a proxy will be generated
23+
* @param newProxyName the fully qualified name to use for the generated proxy
24+
* @return a builder for further customizing the proxy. {@link
25+
* ProxyInjectionBuilder#inject(InjectionMode)} must be called to actually inject the proxy.
26+
*/
27+
ProxyInjectionBuilder proxyBuilder(String classToProxy, String newProxyName);
28+
29+
/**
30+
* Same as invoking {@link #proxyBuilder(String, String)}, but the resulting proxy will have the
31+
* same name as the proxied class.
32+
*
33+
* @param classToProxy the fully qualified name of the class for which a proxy will be generated
34+
* @return a builder for further customizing and injecting the proxy
35+
*/
36+
default ProxyInjectionBuilder proxyBuilder(String classToProxy) {
37+
return proxyBuilder(classToProxy, classToProxy);
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.extension.instrumentation.internal.injection;
7+
8+
/**
9+
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
10+
* any time.
11+
*/
12+
public enum InjectionMode {
13+
CLASS_ONLY
14+
// TODO: implement the modes RESOURCE_ONLY and CLASS_AND_RESOURCE
15+
// This will require a custom URL implementation for byte arrays, similar to how bytebuddy's
16+
// ByteArrayClassLoader does it
17+
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.extension.instrumentation.internal.injection;
7+
8+
/**
9+
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
10+
* any time.
11+
*/
12+
public interface ProxyInjectionBuilder {
13+
14+
void inject(InjectionMode mode);
15+
}

javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java

+22-2
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313

1414
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
1515
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
16+
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
1617
import io.opentelemetry.javaagent.tooling.HelperInjector;
1718
import io.opentelemetry.javaagent.tooling.TransformSafeLogger;
1819
import io.opentelemetry.javaagent.tooling.Utils;
1920
import io.opentelemetry.javaagent.tooling.bytebuddy.LoggingFailSafeMatcher;
2021
import io.opentelemetry.javaagent.tooling.config.AgentConfig;
2122
import io.opentelemetry.javaagent.tooling.field.VirtualFieldImplementationInstaller;
2223
import io.opentelemetry.javaagent.tooling.field.VirtualFieldImplementationInstallerFactory;
24+
import io.opentelemetry.javaagent.tooling.instrumentation.indy.ClassInjectorImpl;
2325
import io.opentelemetry.javaagent.tooling.instrumentation.indy.IndyModuleRegistry;
2426
import io.opentelemetry.javaagent.tooling.instrumentation.indy.IndyTypeTransformerImpl;
2527
import io.opentelemetry.javaagent.tooling.instrumentation.indy.PatchByteCodeVersionTransformer;
@@ -78,8 +80,25 @@ private AgentBuilder installIndyModule(
7880

7981
IndyModuleRegistry.registerIndyModule(instrumentationModule);
8082

83+
HelperResourceBuilderImpl helperResourceBuilder = new HelperResourceBuilderImpl();
84+
instrumentationModule.registerHelperResources(helperResourceBuilder);
85+
86+
ClassInjectorImpl injectedClassesCollector = new ClassInjectorImpl(instrumentationModule);
87+
if (instrumentationModule instanceof ExperimentalInstrumentationModule) {
88+
((ExperimentalInstrumentationModule) instrumentationModule)
89+
.injectClasses(injectedClassesCollector);
90+
}
91+
92+
AgentBuilder.Transformer helperInjector =
93+
new HelperInjector(
94+
instrumentationModule.instrumentationName(),
95+
injectedClassesCollector.getClassesToInject(),
96+
helperResourceBuilder.getResources(),
97+
instrumentationModule.getClass().getClassLoader(),
98+
instrumentation);
99+
81100
// TODO (Jonas): Adapt MuzzleMatcher to use the same type lookup strategy as the
82-
// InstrumentationModuleClassLoader
101+
// InstrumentationModuleClassLoader (see IndyModuleTypePool)
83102
// MuzzleMatcher muzzleMatcher = new MuzzleMatcher(logger, instrumentationModule, config);
84103
VirtualFieldImplementationInstaller contextProvider =
85104
virtualFieldInstallerFactory.create(instrumentationModule);
@@ -88,7 +107,8 @@ private AgentBuilder installIndyModule(
88107
for (TypeInstrumentation typeInstrumentation : instrumentationModule.typeInstrumentations()) {
89108
AgentBuilder.Identified.Extendable extendableAgentBuilder =
90109
setTypeMatcher(agentBuilder, instrumentationModule, typeInstrumentation)
91-
.transform(new PatchByteCodeVersionTransformer());
110+
.transform(new PatchByteCodeVersionTransformer())
111+
.transform(helperInjector);
92112

93113
// TODO (Jonas): we are not calling
94114
// contextProvider.rewriteVirtualFieldsCalls(extendableAgentBuilder) anymore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.tooling.instrumentation.indy;
7+
8+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
9+
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ClassInjector;
10+
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.InjectionMode;
11+
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ProxyInjectionBuilder;
12+
import java.util.HashMap;
13+
import java.util.Map;
14+
import java.util.function.Function;
15+
import net.bytebuddy.description.type.TypeDescription;
16+
import net.bytebuddy.pool.TypePool;
17+
18+
public class ClassInjectorImpl implements ClassInjector {
19+
20+
private final InstrumentationModule instrumentationModule;
21+
22+
private final Map<String, Function<ClassLoader, byte[]>> classesToInject;
23+
24+
private final IndyProxyFactory proxyFactory;
25+
26+
public ClassInjectorImpl(InstrumentationModule module) {
27+
instrumentationModule = module;
28+
classesToInject = new HashMap<>();
29+
proxyFactory = IndyBootstrap.getProxyFactory(module);
30+
}
31+
32+
public Map<String, Function<ClassLoader, byte[]>> getClassesToInject() {
33+
return classesToInject;
34+
}
35+
36+
@Override
37+
public ProxyInjectionBuilder proxyBuilder(String classToProxy, String newProxyName) {
38+
return new ProxyBuilder(classToProxy, newProxyName);
39+
}
40+
41+
private class ProxyBuilder implements ProxyInjectionBuilder {
42+
43+
private final String classToProxy;
44+
private final String proxyClassName;
45+
46+
ProxyBuilder(String classToProxy, String proxyClassName) {
47+
this.classToProxy = classToProxy;
48+
this.proxyClassName = proxyClassName;
49+
}
50+
51+
@Override
52+
public void inject(InjectionMode mode) {
53+
if (mode != InjectionMode.CLASS_ONLY) {
54+
throw new UnsupportedOperationException("Not yet implemented");
55+
}
56+
classesToInject.put(
57+
proxyClassName,
58+
cl -> {
59+
TypePool typePool = IndyModuleTypePool.get(cl, instrumentationModule);
60+
TypeDescription proxiedType = typePool.describe(classToProxy).resolve();
61+
return proxyFactory.generateProxy(proxiedType, proxyClassName).getBytes();
62+
});
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)