Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add capability for invokedynamic InstrumentationModules to inject proxies #9565

Merged
merged 21 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
25a8763
Implemented invokedynamic proxy injection for InstrumentationModules
JonasKunz Sep 26, 2023
c3a6a13
Merge remote-tracking branch 'otel/main' into instrumentation-proxies
JonasKunz Sep 26, 2023
7a03962
Comments and spotless
JonasKunz Sep 27, 2023
9209cc6
Remove unnecessary subpackage
JonasKunz Sep 27, 2023
a54e730
Added missing javadoc
JonasKunz Sep 27, 2023
57da000
spotless fixes
JonasKunz Sep 27, 2023
1aa563e
Enable AWS SDK instrumentation for indy via proxy mechanism
JonasKunz Sep 27, 2023
d48563c
Moved injection to new ExtendedInstrumentationModule
JonasKunz Oct 2, 2023
43fc618
Javadoc review fixes
JonasKunz Oct 2, 2023
80dee2f
Merge remote-tracking branch 'otel/main' into instrumentation-proxies
JonasKunz Oct 2, 2023
5adf59e
Fix AWS tests for indy, enable for testing with indy flag
JonasKunz Oct 2, 2023
ac77780
Moved and renamed ExtendedInstrumentationModule
JonasKunz Oct 4, 2023
6b9f958
Added default method for generating proxy with same name
JonasKunz Oct 4, 2023
466ca1b
Revert AWS changes
JonasKunz Oct 4, 2023
1b3352b
Added proxy in log4j 2.17 instrumentation
JonasKunz Oct 4, 2023
85829bb
Spotless fixes
JonasKunz Oct 4, 2023
4e541ca
Merge remote-tracking branch 'otel/main' into instrumentation-proxies
JonasKunz Oct 13, 2023
0a540a2
Merge remote-tracking branch 'otel/main' into instrumentation-proxies
JonasKunz Oct 16, 2023
cbe27b3
Remove type pool caching
JonasKunz Oct 18, 2023
0a3b736
typo fix
JonasKunz Oct 18, 2023
a788f7c
Merge remote-tracking branch 'otel/HEAD' into instrumentation-proxies
JonasKunz Oct 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ClassInjector;
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.InjectionMode;
import java.util.List;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

@AutoService(InstrumentationModule.class)
public class Log4j2InstrumentationModule extends InstrumentationModule {
public class Log4j2InstrumentationModule extends InstrumentationModule
implements ExperimentalInstrumentationModule {
public Log4j2InstrumentationModule() {
super("log4j-context-data", "log4j-context-data-2.17");
}
Expand All @@ -31,8 +35,11 @@ public void registerHelperResources(HelperResourceBuilder helperResourceBuilder)
}

@Override
public boolean isIndyModule() {
return false;
public void injectClasses(ClassInjector injector) {
injector
.proxyBuilder(
"io.opentelemetry.instrumentation.log4j.contextdata.v2_17.OpenTelemetryContextDataProvider")
.inject(InjectionMode.CLASS_ONLY);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.extension.instrumentation.internal;

import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ClassInjector;

/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public interface ExperimentalInstrumentationModule {

/**
* Only functional for Modules where {@link InstrumentationModule#isIndyModule()} returns {@code
* true}.
*
* <p>Normally, helper and advice classes are loaded in a child classloader of the instrumented
* classloader. This method allows to inject classes directly into the instrumented classloader
* instead.
*
* @param injector the builder for injecting classes
*/
default void injectClasses(ClassInjector injector) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.extension.instrumentation.internal.injection;

/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public interface ClassInjector {

/**
* Create a builder for a proxy class which will be injected into the instrumented {@link
* ClassLoader}. The generated proxy will delegate to the original class, which is loaded in a
* separate classloader.
*
* <p>This removes the need for the proxied class and its dependencies to be visible (just like
* Advices) to the instrumented ClassLoader.
*
* @param classToProxy the fully qualified name of the class for which a proxy will be generated
* @param newProxyName the fully qualified name to use for the generated proxy
* @return a builder for further customizing the proxy. {@link
* ProxyInjectionBuilder#inject(InjectionMode)} must be called to actually inject the proxy.
*/
ProxyInjectionBuilder proxyBuilder(String classToProxy, String newProxyName);
JonasKunz marked this conversation as resolved.
Show resolved Hide resolved

/**
* Same as invoking {@link #proxyBuilder(String, String)}, but the resulting proxy will have the
* same name as the proxied class.
*
* @param classToProxy the fully qualified name of the class for which a proxy will be generated
* @return a builder for further customizing and injecting the proxy
*/
default ProxyInjectionBuilder proxyBuilder(String classToProxy) {
return proxyBuilder(classToProxy, classToProxy);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.extension.instrumentation.internal.injection;

/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public enum InjectionMode {
CLASS_ONLY
// TODO: implement the modes RESOURCE_ONLY and CLASS_AND_RESOURCE
// This will require a custom URL implementation for byte arrays, similar to how bytebuddy's
// ByteArrayClassLoader does it

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.extension.instrumentation.internal.injection;

/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public interface ProxyInjectionBuilder {

void inject(InjectionMode mode);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@

import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
import io.opentelemetry.javaagent.tooling.HelperInjector;
import io.opentelemetry.javaagent.tooling.TransformSafeLogger;
import io.opentelemetry.javaagent.tooling.Utils;
import io.opentelemetry.javaagent.tooling.bytebuddy.LoggingFailSafeMatcher;
import io.opentelemetry.javaagent.tooling.config.AgentConfig;
import io.opentelemetry.javaagent.tooling.field.VirtualFieldImplementationInstaller;
import io.opentelemetry.javaagent.tooling.field.VirtualFieldImplementationInstallerFactory;
import io.opentelemetry.javaagent.tooling.instrumentation.indy.ClassInjectorImpl;
import io.opentelemetry.javaagent.tooling.instrumentation.indy.IndyModuleRegistry;
import io.opentelemetry.javaagent.tooling.instrumentation.indy.IndyTypeTransformerImpl;
import io.opentelemetry.javaagent.tooling.instrumentation.indy.PatchByteCodeVersionTransformer;
Expand Down Expand Up @@ -78,8 +80,25 @@ private AgentBuilder installIndyModule(

IndyModuleRegistry.registerIndyModule(instrumentationModule);

HelperResourceBuilderImpl helperResourceBuilder = new HelperResourceBuilderImpl();
instrumentationModule.registerHelperResources(helperResourceBuilder);

ClassInjectorImpl injectedClassesCollector = new ClassInjectorImpl(instrumentationModule);
if (instrumentationModule instanceof ExperimentalInstrumentationModule) {
((ExperimentalInstrumentationModule) instrumentationModule)
.injectClasses(injectedClassesCollector);
}

AgentBuilder.Transformer helperInjector =
new HelperInjector(
instrumentationModule.instrumentationName(),
injectedClassesCollector.getClassesToInject(),
helperResourceBuilder.getResources(),
instrumentationModule.getClass().getClassLoader(),
instrumentation);

// TODO (Jonas): Adapt MuzzleMatcher to use the same type lookup strategy as the
// InstrumentationModuleClassLoader
// InstrumentationModuleClassLoader (see IndyModuleTypePool)
// MuzzleMatcher muzzleMatcher = new MuzzleMatcher(logger, instrumentationModule, config);
VirtualFieldImplementationInstaller contextProvider =
virtualFieldInstallerFactory.create(instrumentationModule);
Expand All @@ -88,7 +107,8 @@ private AgentBuilder installIndyModule(
for (TypeInstrumentation typeInstrumentation : instrumentationModule.typeInstrumentations()) {
AgentBuilder.Identified.Extendable extendableAgentBuilder =
setTypeMatcher(agentBuilder, instrumentationModule, typeInstrumentation)
.transform(new PatchByteCodeVersionTransformer());
.transform(new PatchByteCodeVersionTransformer())
.transform(helperInjector);

// TODO (Jonas): we are not calling
// contextProvider.rewriteVirtualFieldsCalls(extendableAgentBuilder) anymore
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.tooling.instrumentation.indy;

import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ClassInjector;
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.InjectionMode;
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ProxyInjectionBuilder;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.pool.TypePool;

public class ClassInjectorImpl implements ClassInjector {

private final InstrumentationModule instrumentationModule;

private final Map<String, Function<ClassLoader, byte[]>> classesToInject;

private final IndyProxyFactory proxyFactory;

public ClassInjectorImpl(InstrumentationModule module) {
instrumentationModule = module;
classesToInject = new HashMap<>();
proxyFactory = IndyBootstrap.getProxyFactory(module);
}

public Map<String, Function<ClassLoader, byte[]>> getClassesToInject() {
return classesToInject;
}

@Override
public ProxyInjectionBuilder proxyBuilder(String classToProxy, String newProxyName) {
return new ProxyBuilder(classToProxy, newProxyName);
}

private class ProxyBuilder implements ProxyInjectionBuilder {

private final String classToProxy;
private final String proxyClassName;

ProxyBuilder(String classToProxy, String proxyClassName) {
this.classToProxy = classToProxy;
this.proxyClassName = proxyClassName;
}

@Override
public void inject(InjectionMode mode) {
if (mode != InjectionMode.CLASS_ONLY) {
throw new UnsupportedOperationException("Not yet implemented");
}
classesToInject.put(
proxyClassName,
cl -> {
TypePool typePool = IndyModuleTypePool.get(cl, instrumentationModule);
TypeDescription proxiedType = typePool.describe(classToProxy).resolve();
return proxyFactory.generateProxy(proxiedType, proxyClassName).getBytes();
});
}
}
}
Loading
Loading