Support injecting resources into classloader and use it in aws-sdk-2.… (#1172)
* Support injecting resources into classloader and use it in aws-sdk-2.2 instrumentation. * Use URL for duplication check instead of reading content and inject URLs directly instead of reading to byte array first. * Remove getResource
This commit is contained in:
parent
fd07525744
commit
a9f0e21bfe
|
@ -17,72 +17,46 @@
|
|||
package io.opentelemetry.instrumentation.auto.awssdk.v2_2;
|
||||
|
||||
import static io.opentelemetry.javaagent.tooling.ClassLoaderMatcher.hasClassesNamed;
|
||||
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.implementsInterface;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import io.opentelemetry.javaagent.tooling.Instrumenter;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import software.amazon.awssdk.core.client.builder.SdkClientBuilder;
|
||||
|
||||
/** AWS SDK v2 instrumentation */
|
||||
/**
|
||||
* Injects resource file with reference to our {@link TracingExecutionInterceptor} to allow SDK's
|
||||
* service loading mechanism to pick it up.
|
||||
*/
|
||||
@AutoService(Instrumenter.class)
|
||||
public final class AwsClientInstrumentation extends AbstractAwsClientInstrumentation {
|
||||
|
||||
public class AwsClientInstrumentation extends AbstractAwsClientInstrumentation {
|
||||
@Override
|
||||
public ElementMatcher<ClassLoader> classLoaderMatcher() {
|
||||
// Optimization for expensive typeMatcher.
|
||||
return hasClassesNamed("software.amazon.awssdk.core.client.builder.SdkClientBuilder");
|
||||
public String[] helperResourceNames() {
|
||||
return new String[] {
|
||||
"software/amazon/awssdk/global/handlers/execution.interceptors",
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return nameStartsWith("software.amazon.awssdk.")
|
||||
.and(
|
||||
implementsInterface(
|
||||
named("software.amazon.awssdk.core.client.builder.SdkClientBuilder")));
|
||||
public ElementMatcher<ClassLoader> classLoaderMatcher() {
|
||||
// We don't actually transform it but want to make sure we only apply the instrumentation when
|
||||
// our key dependency is present.
|
||||
return hasClassesNamed("software.amazon.awssdk.core.interceptor.ExecutionInterceptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher<? super TypeDescription> typeMatcher() {
|
||||
// We don't actually need to transform anything but need a class to match to make sure our
|
||||
// helpers are injected. Pick an arbitrary class we happen to reference.
|
||||
return named("software.amazon.awssdk.core.SdkRequest");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||
Map<ElementMatcher.Junction<MethodDescription>, String> transformers = new HashMap<>();
|
||||
|
||||
transformers.put(
|
||||
isMethod().and(isPublic()).and(named("build")),
|
||||
AwsClientInstrumentation.class.getName() + "$AwsSdkClientBuilderBuildAdvice");
|
||||
|
||||
transformers.put(
|
||||
isMethod().and(isPublic()).and(named("overrideConfiguration")),
|
||||
AwsClientInstrumentation.class.getName()
|
||||
+ "$AwsSdkClientBuilderOverrideConfigurationAdvice");
|
||||
|
||||
return Collections.unmodifiableMap(transformers);
|
||||
}
|
||||
|
||||
public static class AwsSdkClientBuilderOverrideConfigurationAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void methodEnter(@Advice.This SdkClientBuilder thiz) {
|
||||
TracingExecutionInterceptor.OVERRIDDEN.put(thiz, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AwsSdkClientBuilderBuildAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void methodEnter(@Advice.This SdkClientBuilder thiz) {
|
||||
if (!Boolean.TRUE.equals(TracingExecutionInterceptor.OVERRIDDEN.get(thiz))) {
|
||||
TracingExecutionInterceptor.overrideConfiguration(thiz);
|
||||
}
|
||||
}
|
||||
// Nothing to do, helpers are injected but no class transformation happens here.
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.opentelemetry.instrumentation.auto.awssdk.v2_2;
|
||||
|
||||
import static io.opentelemetry.javaagent.tooling.ClassLoaderMatcher.hasClassesNamed;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import io.opentelemetry.javaagent.tooling.Instrumenter;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
|
||||
|
||||
/**
|
||||
* Separate instrumentation to inject into user configuration overrides. Overrides aren't merged so
|
||||
* we need to either inject into their override or create our own, but not both.
|
||||
*/
|
||||
@AutoService(Instrumenter.class)
|
||||
public final class AwsClientOverrideInstrumentation extends AbstractAwsClientInstrumentation {
|
||||
|
||||
@Override
|
||||
public ElementMatcher<ClassLoader> classLoaderMatcher() {
|
||||
// Optimization for expensive typeMatcher.
|
||||
return hasClassesNamed("software.amazon.awssdk.core.client.config.ClientOverrideConfiguration");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return named("software.amazon.awssdk.core.client.config.ClientOverrideConfiguration");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||
return Collections.singletonMap(
|
||||
isMethod().and(isPublic()).and(isStatic()).and(named("builder")),
|
||||
AwsClientOverrideInstrumentation.class.getName() + "$AwsSdkClientOverrideAdvice");
|
||||
}
|
||||
|
||||
public static class AwsSdkClientOverrideAdvice {
|
||||
|
||||
@Advice.OnMethodExit(suppress = Throwable.class)
|
||||
public static void methodEnter(@Advice.Return ClientOverrideConfiguration.Builder builder) {
|
||||
TracingExecutionInterceptor.overrideConfiguration(builder);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,24 +16,18 @@
|
|||
|
||||
package io.opentelemetry.instrumentation.auto.awssdk.v2_2;
|
||||
|
||||
import static io.opentelemetry.instrumentation.auto.api.WeakMap.Provider.newWeakMap;
|
||||
|
||||
import io.opentelemetry.context.ContextUtils;
|
||||
import io.opentelemetry.context.Scope;
|
||||
import io.opentelemetry.instrumentation.api.decorator.ClientDecorator;
|
||||
import io.opentelemetry.instrumentation.auto.api.WeakMap;
|
||||
import io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdk;
|
||||
import io.opentelemetry.trace.Span;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import org.reactivestreams.Publisher;
|
||||
import software.amazon.awssdk.core.SdkRequest;
|
||||
import software.amazon.awssdk.core.SdkResponse;
|
||||
import software.amazon.awssdk.core.async.AsyncRequestBody;
|
||||
import software.amazon.awssdk.core.client.builder.SdkClientBuilder;
|
||||
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
|
||||
import software.amazon.awssdk.core.interceptor.Context.AfterExecution;
|
||||
import software.amazon.awssdk.core.interceptor.Context.AfterMarshalling;
|
||||
import software.amazon.awssdk.core.interceptor.Context.AfterTransmission;
|
||||
|
@ -60,45 +54,14 @@ import software.amazon.awssdk.http.SdkHttpResponse;
|
|||
*/
|
||||
public class TracingExecutionInterceptor implements ExecutionInterceptor {
|
||||
|
||||
// Keeps track of SDK clients that have been overridden by the user and don't need to be
|
||||
// overridden by us.
|
||||
public static final WeakMap<SdkClientBuilder, Boolean> OVERRIDDEN = newWeakMap();
|
||||
|
||||
public static class ScopeHolder {
|
||||
public static final ThreadLocal<Scope> CURRENT = new ThreadLocal<>();
|
||||
}
|
||||
|
||||
// Note: it looks like this lambda doesn't get generated as a separate class file so we do not
|
||||
// need to inject helper for it.
|
||||
private static final Consumer<ClientOverrideConfiguration.Builder>
|
||||
OVERRIDE_CONFIGURATION_CONSUMER =
|
||||
builder ->
|
||||
builder.addExecutionInterceptor(
|
||||
new TracingExecutionInterceptor(AwsSdk.newInterceptor()));
|
||||
|
||||
private final ExecutionInterceptor delegate;
|
||||
|
||||
private TracingExecutionInterceptor(ExecutionInterceptor delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* We keep this method here because it references Java8 classes and we would like to avoid
|
||||
* compiling this for instrumentation code that should load into Java7.
|
||||
*/
|
||||
public static void overrideConfiguration(SdkClientBuilder client) {
|
||||
// We intercept calls to overrideConfiguration to make sure when a user overrides the
|
||||
// configuration, we join their configuration. This means all we need to do is call the method
|
||||
// here and we will intercept the builder and add our interceptor.
|
||||
client.overrideConfiguration(builder -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
* We keep this method here because it references Java8 classes and we would like to avoid
|
||||
* compiling this for instrumentation code that should load into Java7.
|
||||
*/
|
||||
public static void overrideConfiguration(ClientOverrideConfiguration.Builder builder) {
|
||||
OVERRIDE_CONFIGURATION_CONSUMER.accept(builder);
|
||||
public TracingExecutionInterceptor() {
|
||||
delegate = AwsSdk.newInterceptor();
|
||||
}
|
||||
|
||||
public static void muzzleCheck() {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
io.opentelemetry.instrumentation.auto.awssdk.v2_2.TracingExecutionInterceptor
|
|
@ -52,7 +52,9 @@ import software.amazon.awssdk.services.sqs.model.CreateQueueRequest
|
|||
import software.amazon.awssdk.services.sqs.model.SendMessageRequest
|
||||
import spock.lang.AutoCleanup
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Unroll
|
||||
|
||||
@Unroll
|
||||
abstract class AbstractAws2ClientTest extends InstrumentationSpecification {
|
||||
|
||||
private static final StaticCredentialsProvider CREDENTIALS_PROVIDER = StaticCredentialsProvider
|
||||
|
|
|
@ -1 +1,7 @@
|
|||
apply from: "$rootDir/gradle/instrumentation.gradle"
|
||||
|
||||
dependencies {
|
||||
compileOnly project(':javaagent-bootstrap')
|
||||
|
||||
testImplementation project(':javaagent-bootstrap')
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.opentelemetry.instrumentation.auto.javaclassloader;
|
||||
|
||||
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.extendsClass;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import io.opentelemetry.javaagent.bootstrap.HelperResources;
|
||||
import io.opentelemetry.javaagent.tooling.Instrumenter;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
|
||||
/**
|
||||
* Instruments {@link ClassLoader} to have calls to get resources intercepted and check our map of
|
||||
* helper resources that is filled by instrumentation when they need helpers.
|
||||
*
|
||||
* <p>We currently only intercept {@link ClassLoader#getResources(String)} because this is the case
|
||||
* we are currently always interested in, where it's used for service loading.
|
||||
*/
|
||||
@AutoService(Instrumenter.class)
|
||||
public class ResourceInjectionInstrumentation extends Instrumenter.Default {
|
||||
|
||||
public ResourceInjectionInstrumentation() {
|
||||
super("class-loader");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher<? super TypeDescription> typeMatcher() {
|
||||
return extendsClass(named("java.lang.ClassLoader"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||
return Collections.singletonMap(
|
||||
isMethod().and(named("getResources")).and(takesArguments(String.class)),
|
||||
ResourceInjectionInstrumentation.class.getName() + "$GetResourcesAdvice");
|
||||
}
|
||||
|
||||
public static class GetResourcesAdvice {
|
||||
@Advice.OnMethodExit(suppress = Throwable.class)
|
||||
public static void onExit(
|
||||
@Advice.This ClassLoader classLoader,
|
||||
@Advice.Argument(0) String name,
|
||||
@Advice.Return(readOnly = false) Enumeration<URL> resources) {
|
||||
URL helper = HelperResources.load(classLoader, name);
|
||||
if (helper == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!resources.hasMoreElements()) {
|
||||
resources = Collections.enumeration(Collections.singleton(helper));
|
||||
return;
|
||||
}
|
||||
|
||||
List<URL> result = Collections.list(resources);
|
||||
boolean duplicate = false;
|
||||
for (URL loadedUrl : result) {
|
||||
if (helper.sameFile(loadedUrl)) {
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!duplicate) {
|
||||
result.add(helper);
|
||||
}
|
||||
|
||||
resources = Collections.enumeration(result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import static io.opentelemetry.auto.util.gc.GCUtils.awaitGC
|
||||
|
||||
import io.opentelemetry.auto.test.AgentTestRunner
|
||||
import io.opentelemetry.javaagent.tooling.HelperInjector
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
class ResourceInjectionTest extends AgentTestRunner {
|
||||
|
||||
def "resources injected to non-delegating classloader"() {
|
||||
setup:
|
||||
String resourceName = 'test-resources/test-resource.txt'
|
||||
HelperInjector injector = new HelperInjector("test", [], [resourceName])
|
||||
AtomicReference<URLClassLoader> emptyLoader = new AtomicReference<>(new URLClassLoader(new URL[0], (ClassLoader) null))
|
||||
|
||||
when:
|
||||
def resourceUrls = emptyLoader.get().getResources(resourceName)
|
||||
then:
|
||||
!resourceUrls.hasMoreElements()
|
||||
|
||||
when:
|
||||
URLClassLoader notInjectedLoader = new URLClassLoader(new URL[0], (ClassLoader) null)
|
||||
|
||||
injector.transform(null, null, emptyLoader.get(), null)
|
||||
resourceUrls = emptyLoader.get().getResources(resourceName)
|
||||
|
||||
then:
|
||||
resourceUrls.hasMoreElements()
|
||||
resourceUrls.nextElement().openStream().text.trim() == 'Hello world!'
|
||||
|
||||
!notInjectedLoader.getResources(resourceName).hasMoreElements()
|
||||
|
||||
when: "references to emptyLoader are gone"
|
||||
emptyLoader.get().close() // cleanup
|
||||
def ref = new WeakReference(emptyLoader.get())
|
||||
emptyLoader.set(null)
|
||||
|
||||
awaitGC(ref)
|
||||
|
||||
then: "HelperInjector doesn't prevent it from being collected"
|
||||
null == ref.get()
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
Hello world!
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.bootstrap;
|
||||
|
||||
import static io.opentelemetry.instrumentation.auto.api.WeakMap.Provider.newWeakMap;
|
||||
|
||||
import io.opentelemetry.instrumentation.auto.api.WeakMap;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* A holder of resources needed by instrumentation. We store them in the bootstrap classloader so
|
||||
* instrumentation can store from the agent classloader and apps can retrieve from the app
|
||||
* classloader.
|
||||
*/
|
||||
public final class HelperResources {
|
||||
|
||||
private static final WeakMap<ClassLoader, Map<String, URL>> RESOURCES = newWeakMap();
|
||||
|
||||
/** Registers the {@code payload} to be available to instrumentation at {@code path}. */
|
||||
public static void register(ClassLoader classLoader, String path, URL url) {
|
||||
RESOURCES.putIfAbsent(classLoader, new ConcurrentHashMap<String, URL>());
|
||||
RESOURCES.get(classLoader).put(path, url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link URL} that can be used to retrieve the content of the resource at {@code path},
|
||||
* or {@code null} if no resource could be found at {@code path}.
|
||||
*/
|
||||
public static URL load(ClassLoader classLoader, String path) {
|
||||
Map<String, URL> map = RESOURCES.get(classLoader);
|
||||
if (map == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return map.get(path);
|
||||
}
|
||||
|
||||
private HelperResources() {}
|
||||
}
|
|
@ -20,12 +20,13 @@ import static io.opentelemetry.instrumentation.auto.api.WeakMap.Provider.newWeak
|
|||
import static io.opentelemetry.javaagent.tooling.ClassLoaderMatcher.BOOTSTRAP_CLASSLOADER;
|
||||
|
||||
import io.opentelemetry.instrumentation.auto.api.WeakMap;
|
||||
import io.opentelemetry.javaagent.bootstrap.HelperResources;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.security.SecureClassLoader;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -61,6 +62,7 @@ public class HelperInjector implements Transformer {
|
|||
private final String requestingName;
|
||||
|
||||
private final Set<String> helperClassNames;
|
||||
private final Set<String> helperResourceNames;
|
||||
private final Map<String, byte[]> dynamicTypeMap = new LinkedHashMap<>();
|
||||
|
||||
private final WeakMap<ClassLoader, Boolean> injectedClassLoaders = newWeakMap();
|
||||
|
@ -75,10 +77,12 @@ public class HelperInjector implements Transformer {
|
|||
* order provided. This is important if there is interdependency between helper classes that
|
||||
* requires them to be injected in a specific order.
|
||||
*/
|
||||
public HelperInjector(String requestingName, String... helperClassNames) {
|
||||
public HelperInjector(
|
||||
String requestingName, List<String> helperClassNames, List<String> helperResourceNames) {
|
||||
this.requestingName = requestingName;
|
||||
|
||||
this.helperClassNames = new LinkedHashSet<>(Arrays.asList(helperClassNames));
|
||||
this.helperClassNames = new LinkedHashSet<>(helperClassNames);
|
||||
this.helperResourceNames = new LinkedHashSet<>(helperResourceNames);
|
||||
}
|
||||
|
||||
public HelperInjector(String requestingName, Map<String, byte[]> helperMap) {
|
||||
|
@ -86,6 +90,8 @@ public class HelperInjector implements Transformer {
|
|||
|
||||
helperClassNames = helperMap.keySet();
|
||||
dynamicTypeMap.putAll(helperMap);
|
||||
|
||||
helperResourceNames = Collections.emptySet();
|
||||
}
|
||||
|
||||
public static HelperInjector forDynamicTypes(
|
||||
|
@ -161,6 +167,19 @@ public class HelperInjector implements Transformer {
|
|||
|
||||
ensureModuleCanReadHelperModules(module);
|
||||
}
|
||||
|
||||
if (!helperResourceNames.isEmpty()) {
|
||||
for (String resourceName : helperResourceNames) {
|
||||
URL resource = Utils.getAgentClassLoader().getResource(resourceName);
|
||||
if (resource == null) {
|
||||
log.debug("Helper resource {} requested but not found.", resourceName);
|
||||
continue;
|
||||
}
|
||||
|
||||
HelperResources.register(classLoader, resourceName, resource);
|
||||
}
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
|
|
@ -66,6 +66,8 @@ public interface Instrumenter {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(Default.class);
|
||||
|
||||
private static final String[] EMPTY = new String[0];
|
||||
|
||||
// Added here instead of AgentInstaller's ignores because it's relatively
|
||||
// expensive. https://github.com/DataDog/dd-trace-java/pull/1045
|
||||
public static final Junction<AnnotationSource> NOT_DECORATOR_MATCHER =
|
||||
|
@ -123,10 +125,14 @@ public interface Instrumenter {
|
|||
private AgentBuilder.Identified.Extendable injectHelperClasses(
|
||||
AgentBuilder.Identified.Extendable agentBuilder) {
|
||||
String[] helperClassNames = helperClassNames();
|
||||
if (helperClassNames.length > 0) {
|
||||
String[] helperResourceNames = helperResourceNames();
|
||||
if (helperClassNames.length > 0 || helperResourceNames.length > 0) {
|
||||
agentBuilder =
|
||||
agentBuilder.transform(
|
||||
new HelperInjector(getClass().getSimpleName(), helperClassNames));
|
||||
new HelperInjector(
|
||||
getClass().getSimpleName(),
|
||||
Arrays.asList(helperClassNames),
|
||||
Arrays.asList(helperResourceNames)));
|
||||
}
|
||||
return agentBuilder;
|
||||
}
|
||||
|
@ -201,7 +207,12 @@ public interface Instrumenter {
|
|||
|
||||
/** @return Class names of helpers to inject into the user's classloader */
|
||||
public String[] helperClassNames() {
|
||||
return new String[0];
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
/** @return Resource names to inject into the user's classloader */
|
||||
public String[] helperResourceNames() {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
/** @return A type matcher used to match the classloader under transform */
|
||||
|
|
|
@ -115,6 +115,10 @@ public class GlobalIgnoresMatcher<T extends TypeDescription>
|
|||
if (name.startsWith("java.rmi.") || name.startsWith("java.util.concurrent.")) {
|
||||
return false;
|
||||
}
|
||||
if (name.equals("java.lang.ClassLoader")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Concurrent instrumentation modifies the structure of
|
||||
// Cleaner class incompatibly with java9+ modules.
|
||||
// Working around until a long-term fix for modules can be
|
||||
|
|
|
@ -38,7 +38,7 @@ class HelperInjectionTest extends AgentSpecification {
|
|||
def "helpers injected to non-delegating classloader"() {
|
||||
setup:
|
||||
String helperClassName = HelperInjectionTest.getPackage().getName() + '.HelperClass'
|
||||
HelperInjector injector = new HelperInjector("test", helperClassName)
|
||||
HelperInjector injector = new HelperInjector("test", [helperClassName], [])
|
||||
AtomicReference<URLClassLoader> emptyLoader = new AtomicReference<>(new URLClassLoader(new URL[0], (ClassLoader) null))
|
||||
|
||||
when:
|
||||
|
@ -70,7 +70,7 @@ class HelperInjectionTest extends AgentSpecification {
|
|||
ByteBuddyAgent.install()
|
||||
AgentInstaller.installBytebuddyAgent(ByteBuddyAgent.getInstrumentation())
|
||||
String helperClassName = HelperInjectionTest.getPackage().getName() + '.HelperClass'
|
||||
HelperInjector injector = new HelperInjector("test", helperClassName)
|
||||
HelperInjector injector = new HelperInjector("test", [helperClassName], [])
|
||||
URLClassLoader bootstrapChild = new URLClassLoader(new URL[0], (ClassLoader) null)
|
||||
|
||||
when:
|
||||
|
|
Loading…
Reference in New Issue