Transform lambda classes (#4182)
* Transform lambda classes * improve comment
This commit is contained in:
parent
ae7ad4b7e8
commit
559cdcbbee
|
@ -15,11 +15,8 @@ import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
|||
import io.opentelemetry.javaagent.instrumentation.api.ContextStore;
|
||||
import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext;
|
||||
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
|
||||
import io.opentelemetry.javaagent.instrumentation.api.concurrent.CallableWrapper;
|
||||
import io.opentelemetry.javaagent.instrumentation.api.concurrent.ExecutorAdviceHelper;
|
||||
import io.opentelemetry.javaagent.instrumentation.api.concurrent.PropagatedContext;
|
||||
import io.opentelemetry.javaagent.instrumentation.api.concurrent.RunnableWrapper;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Callable;
|
||||
|
@ -74,7 +71,6 @@ public class JavaExecutorInstrumentation extends AbstractExecutorInstrumentation
|
|||
@Advice.Argument(value = 0, readOnly = false) Runnable task) {
|
||||
Context context = Java8BytecodeBridge.currentContext();
|
||||
if (ExecutorAdviceHelper.shouldPropagateContext(context, task)) {
|
||||
task = RunnableWrapper.wrapIfNeeded(task);
|
||||
ContextStore<Runnable, PropagatedContext> contextStore =
|
||||
InstrumentationContext.get(Runnable.class, PropagatedContext.class);
|
||||
return ExecutorAdviceHelper.attachContextToTask(context, contextStore, task);
|
||||
|
@ -119,7 +115,6 @@ public class JavaExecutorInstrumentation extends AbstractExecutorInstrumentation
|
|||
@Advice.Argument(value = 0, readOnly = false) Runnable task) {
|
||||
Context context = Java8BytecodeBridge.currentContext();
|
||||
if (ExecutorAdviceHelper.shouldPropagateContext(context, task)) {
|
||||
task = RunnableWrapper.wrapIfNeeded(task);
|
||||
ContextStore<Runnable, PropagatedContext> contextStore =
|
||||
InstrumentationContext.get(Runnable.class, PropagatedContext.class);
|
||||
return ExecutorAdviceHelper.attachContextToTask(context, contextStore, task);
|
||||
|
@ -149,7 +144,6 @@ public class JavaExecutorInstrumentation extends AbstractExecutorInstrumentation
|
|||
@Advice.Argument(value = 0, readOnly = false) Callable<?> task) {
|
||||
Context context = Java8BytecodeBridge.currentContext();
|
||||
if (ExecutorAdviceHelper.shouldPropagateContext(context, task)) {
|
||||
task = CallableWrapper.wrapIfNeeded(task);
|
||||
ContextStore<Callable<?>, PropagatedContext> contextStore =
|
||||
InstrumentationContext.get(Callable.class, PropagatedContext.class);
|
||||
return ExecutorAdviceHelper.attachContextToTask(context, contextStore, task);
|
||||
|
@ -181,21 +175,15 @@ public class JavaExecutorInstrumentation extends AbstractExecutorInstrumentation
|
|||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Collection<Callable<?>> wrappedTasks = new ArrayList<>(tasks.size());
|
||||
Context context = Java8BytecodeBridge.currentContext();
|
||||
for (Callable<?> task : tasks) {
|
||||
if (ExecutorAdviceHelper.shouldPropagateContext(context, task)) {
|
||||
Callable<?> newTask = CallableWrapper.wrapIfNeeded(task);
|
||||
wrappedTasks.add(newTask);
|
||||
ContextStore<Callable<?>, PropagatedContext> contextStore =
|
||||
InstrumentationContext.get(Callable.class, PropagatedContext.class);
|
||||
ExecutorAdviceHelper.attachContextToTask(context, contextStore, newTask);
|
||||
} else {
|
||||
// note that task may be null here
|
||||
wrappedTasks.add(task);
|
||||
ExecutorAdviceHelper.attachContextToTask(context, contextStore, task);
|
||||
}
|
||||
}
|
||||
tasks = wrappedTasks;
|
||||
|
||||
// returning tasks and not propagatedContexts to avoid allocating another list just for an
|
||||
// edge case (exception)
|
||||
return tasks;
|
||||
|
@ -203,8 +191,7 @@ public class JavaExecutorInstrumentation extends AbstractExecutorInstrumentation
|
|||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void submitExit(
|
||||
@Advice.Enter Collection<? extends Callable<?>> wrappedTasks,
|
||||
@Advice.Thrown Throwable throwable) {
|
||||
@Advice.Enter Collection<? extends Callable<?>> tasks, @Advice.Thrown Throwable throwable) {
|
||||
/*
|
||||
Note1: invokeAny doesn't return any futures so all we need to do for it
|
||||
is to make sure we close all scopes in case of an exception.
|
||||
|
@ -215,7 +202,7 @@ public class JavaExecutorInstrumentation extends AbstractExecutorInstrumentation
|
|||
(according to ExecutorService docs and AbstractExecutorService code)
|
||||
*/
|
||||
if (throwable != null) {
|
||||
for (Callable<?> task : wrappedTasks) {
|
||||
for (Callable<?> task : tasks) {
|
||||
if (task != null) {
|
||||
ContextStore<Callable<?>, PropagatedContext> contextStore =
|
||||
InstrumentationContext.get(Callable.class, PropagatedContext.class);
|
||||
|
|
|
@ -16,7 +16,6 @@ import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext;
|
|||
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
|
||||
import io.opentelemetry.javaagent.instrumentation.api.concurrent.ExecutorAdviceHelper;
|
||||
import io.opentelemetry.javaagent.instrumentation.api.concurrent.PropagatedContext;
|
||||
import io.opentelemetry.javaagent.instrumentation.api.concurrent.RunnableWrapper;
|
||||
import java.util.concurrent.Executor;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
|
@ -55,7 +54,6 @@ public class GuavaListenableFutureInstrumentation implements TypeInstrumentation
|
|||
@Advice.Argument(value = 0, readOnly = false) Runnable task) {
|
||||
Context context = Java8BytecodeBridge.currentContext();
|
||||
if (ExecutorAdviceHelper.shouldPropagateContext(context, task)) {
|
||||
task = RunnableWrapper.wrapIfNeeded(task);
|
||||
ContextStore<Runnable, PropagatedContext> contextStore =
|
||||
InstrumentationContext.get(Runnable.class, PropagatedContext.class);
|
||||
return ExecutorAdviceHelper.attachContextToTask(context, contextStore, task);
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
plugins {
|
||||
id("otel.javaagent-instrumentation")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":javaagent-bootstrap"))
|
||||
|
||||
testImplementation(project(":javaagent-bootstrap"))
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.internal.lambda;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
||||
import net.bytebuddy.asm.AsmVisitorWrapper;
|
||||
import net.bytebuddy.description.field.FieldDescription;
|
||||
import net.bytebuddy.description.field.FieldList;
|
||||
import net.bytebuddy.description.method.MethodList;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.implementation.Implementation;
|
||||
import net.bytebuddy.jar.asm.ClassVisitor;
|
||||
import net.bytebuddy.jar.asm.ClassWriter;
|
||||
import net.bytebuddy.jar.asm.MethodVisitor;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import net.bytebuddy.jar.asm.Type;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import net.bytebuddy.pool.TypePool;
|
||||
|
||||
public class InnerClassLambdaMetafactoryInstrumentation implements TypeInstrumentation {
|
||||
|
||||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return named("java.lang.invoke.InnerClassLambdaMetafactory");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transform(TypeTransformer transformer) {
|
||||
transformer.applyTransformer(
|
||||
(builder, typeDescription, classLoader, module) ->
|
||||
builder.visit(
|
||||
new AsmVisitorWrapper() {
|
||||
@Override
|
||||
public int mergeWriter(int flags) {
|
||||
return flags | ClassWriter.COMPUTE_MAXS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int mergeReader(int flags) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassVisitor wrap(
|
||||
TypeDescription instrumentedType,
|
||||
ClassVisitor classVisitor,
|
||||
Implementation.Context implementationContext,
|
||||
TypePool typePool,
|
||||
FieldList<FieldDescription.InDefinedShape> fields,
|
||||
MethodList<?> methods,
|
||||
int writerFlags,
|
||||
int readerFlags) {
|
||||
return new MetaFactoryClassVisitor(
|
||||
classVisitor, instrumentedType.getInternalName());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private static class MetaFactoryClassVisitor extends ClassVisitor {
|
||||
private final String slashClassName;
|
||||
|
||||
MetaFactoryClassVisitor(ClassVisitor cv, String slashClassName) {
|
||||
super(Opcodes.ASM7, cv);
|
||||
this.slashClassName = slashClassName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(
|
||||
int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||
// The version of InnerClassLambdaMetafactory used in first version of jdk8 can be seen at
|
||||
// https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java
|
||||
// Depending on jdk version we instrument either spinInnerClass or generateInnerClass.
|
||||
// We look for a call to ASM ClassWriter.toByteArray() and insert our lambda class
|
||||
// transformation after it so that defining lambda class will proceed with replaced bytecode.
|
||||
// This transformation uses ASM instead of Byte-Buddy advice because advice allows adding
|
||||
// code to the start and end of the method, but here we are modifying a call in the middle of
|
||||
// the method.
|
||||
if (("spinInnerClass".equals(name) || "generateInnerClass".equals(name))
|
||||
&& "()Ljava/lang/Class;".equals(descriptor)) {
|
||||
mv =
|
||||
new MethodVisitor(api, mv) {
|
||||
@Override
|
||||
public void visitMethodInsn(
|
||||
int opcode, String owner, String name, String descriptor, boolean isInterface) {
|
||||
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
|
||||
// if current instruction is a call to ASM ClassWriter.toByteArray() insert call to
|
||||
// our lambda transformer
|
||||
if (opcode == Opcodes.INVOKEVIRTUAL
|
||||
&& "toByteArray".equals(name)
|
||||
&& "()[B".equals(descriptor)) {
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitFieldInsn(
|
||||
Opcodes.GETFIELD, slashClassName, "lambdaClassName", "Ljava/lang/String;");
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
// targetClass is used to get the ClassLoader where lambda class will be defined
|
||||
mv.visitFieldInsn(
|
||||
Opcodes.GETFIELD, slashClassName, "targetClass", "Ljava/lang/Class;");
|
||||
mv.visitMethodInsn(
|
||||
Opcodes.INVOKESTATIC,
|
||||
Type.getInternalName(LambdaTransformer.class),
|
||||
"transform",
|
||||
"([BLjava/lang/String;Ljava/lang/Class;)[B",
|
||||
false);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return mv;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.internal.lambda;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||
import java.util.List;
|
||||
|
||||
@AutoService(InstrumentationModule.class)
|
||||
public class LambdaInstrumentationModule extends InstrumentationModule {
|
||||
public LambdaInstrumentationModule() {
|
||||
super("internal-lambda");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean defaultEnabled() {
|
||||
// internal instrumentations are always enabled by default
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getMuzzleHelperClassNames() {
|
||||
// this instrumentation uses ASM not ByteBuddy so muzzle doesn't automatically add helper
|
||||
// classes
|
||||
return singletonList(
|
||||
"io.opentelemetry.javaagent.instrumentation.internal.lambda.LambdaTransformer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TypeInstrumentation> typeInstrumentations() {
|
||||
return singletonList(new InnerClassLambdaMetafactoryInstrumentation());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.internal.lambda;
|
||||
|
||||
import io.opentelemetry.javaagent.bootstrap.ClassFileTransformerHolder;
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
|
||||
/** Helper class for transforming lambda class bytes. */
|
||||
public final class LambdaTransformer {
|
||||
|
||||
private LambdaTransformer() {}
|
||||
|
||||
/**
|
||||
* Called from {@code java.lang.invoke.InnerClassLambdaMetafactory} to transform lambda class
|
||||
* bytes.
|
||||
*/
|
||||
public static byte[] transform(byte[] classBytes, String slashClassName, Class<?> targetClass) {
|
||||
ClassFileTransformer transformer = ClassFileTransformerHolder.getClassFileTransformer();
|
||||
if (transformer != null) {
|
||||
try {
|
||||
byte[] result =
|
||||
transformer.transform(
|
||||
targetClass.getClassLoader(), slashClassName, null, null, classBytes);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
// sun.instrument.TransformerManager catches Throwable from ClassFileTransformer and ignores
|
||||
// it, we do the same.
|
||||
}
|
||||
}
|
||||
|
||||
return classBytes;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
|
||||
import io.opentelemetry.javaagent.bootstrap.FieldBackedContextStoreAppliedMarker
|
||||
|
||||
class LambdaInstrumentationTest extends AgentInstrumentationSpecification {
|
||||
|
||||
def "test transform Runnable lambda"() {
|
||||
setup:
|
||||
Runnable runnable = TestLambda.makeRunnable()
|
||||
|
||||
expect:
|
||||
// RunnableInstrumentation adds a ContextStore to all implementors of Runnable. If lambda class
|
||||
// is transformed then it must have context store marker interface.
|
||||
runnable instanceof FieldBackedContextStoreAppliedMarker
|
||||
!FieldBackedContextStoreAppliedMarker.isAssignableFrom(Runnable)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
public class TestLambda {
|
||||
static Runnable makeRunnable() {
|
||||
return () -> {};
|
||||
}
|
||||
}
|
|
@ -17,7 +17,6 @@ import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext;
|
|||
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
|
||||
import io.opentelemetry.javaagent.instrumentation.api.concurrent.ExecutorAdviceHelper;
|
||||
import io.opentelemetry.javaagent.instrumentation.api.concurrent.PropagatedContext;
|
||||
import io.opentelemetry.javaagent.instrumentation.api.concurrent.RunnableWrapper;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
|
@ -44,7 +43,6 @@ public class JettyQueuedThreadPoolInstrumentation implements TypeInstrumentation
|
|||
@Advice.Argument(value = 0, readOnly = false) Runnable task) {
|
||||
Context context = Java8BytecodeBridge.currentContext();
|
||||
if (ExecutorAdviceHelper.shouldPropagateContext(context, task)) {
|
||||
task = RunnableWrapper.wrapIfNeeded(task);
|
||||
ContextStore<Runnable, PropagatedContext> contextStore =
|
||||
InstrumentationContext.get(Runnable.class, PropagatedContext.class);
|
||||
return ExecutorAdviceHelper.attachContextToTask(context, contextStore, task);
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
import io.opentelemetry.api.trace.SpanKind
|
||||
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
|
||||
import io.opentelemetry.javaagent.instrumentation.jetty.JavaLambdaMaker
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool
|
||||
|
||||
import static org.junit.Assume.assumeTrue
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.jetty;
|
||||
|
||||
public class JavaLambdaMaker {
|
||||
|
||||
@SuppressWarnings("FunctionalExpressionCanBeFolded")
|
|
@ -19,7 +19,6 @@ import io.opentelemetry.javaagent.instrumentation.api.InstrumentationContext;
|
|||
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
|
||||
import io.opentelemetry.javaagent.instrumentation.api.concurrent.ExecutorAdviceHelper;
|
||||
import io.opentelemetry.javaagent.instrumentation.api.concurrent.PropagatedContext;
|
||||
import io.opentelemetry.javaagent.instrumentation.api.concurrent.RunnableWrapper;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
|
@ -50,7 +49,6 @@ public class SimpleAsyncTaskExecutorInstrumentation implements TypeInstrumentati
|
|||
@Advice.Argument(value = 0, readOnly = false) Runnable task) {
|
||||
Context context = Java8BytecodeBridge.currentContext();
|
||||
if (ExecutorAdviceHelper.shouldPropagateContext(context, task)) {
|
||||
task = RunnableWrapper.wrapIfNeeded(task);
|
||||
ContextStore<Runnable, PropagatedContext> contextStore =
|
||||
InstrumentationContext.get(Runnable.class, PropagatedContext.class);
|
||||
return ExecutorAdviceHelper.attachContextToTask(context, contextStore, task);
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.bootstrap;
|
||||
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
|
||||
/**
|
||||
* Holder for {@link ClassFileTransformer} used by the instrumentation. Calling transform on this
|
||||
* class file transformer processes given bytes the same way as they would be processed during
|
||||
* loading of the class.
|
||||
*/
|
||||
public final class ClassFileTransformerHolder {
|
||||
|
||||
private static volatile ClassFileTransformer classFileTransformer;
|
||||
|
||||
public static ClassFileTransformer getClassFileTransformer() {
|
||||
return classFileTransformer;
|
||||
}
|
||||
|
||||
public static void setClassFileTransformer(ClassFileTransformer transformer) {
|
||||
classFileTransformer = transformer;
|
||||
}
|
||||
|
||||
private ClassFileTransformerHolder() {}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.api.concurrent;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This is used to wrap lambda callables since currently we cannot instrument them.
|
||||
*
|
||||
* <p>FIXME: We should remove this once https://github.com/raphw/byte-buddy/issues/558 is fixed
|
||||
*/
|
||||
public final class CallableWrapper<T> implements Callable<T> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CallableWrapper.class);
|
||||
|
||||
public static Callable<?> wrapIfNeeded(Callable<?> task) {
|
||||
// We wrap only lambdas' anonymous classes and if given object has not already been wrapped.
|
||||
// Anonymous classes have '/' in class name which is not allowed in 'normal' classes.
|
||||
if (task.getClass().getName().contains("/") && !(task instanceof CallableWrapper)) {
|
||||
logger.debug("Wrapping callable task {}", task);
|
||||
return new CallableWrapper(task);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
private final Callable<T> callable;
|
||||
|
||||
private CallableWrapper(Callable<T> callable) {
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T call() throws Exception {
|
||||
return callable.call();
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.api.concurrent;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This is used to wrap lambda runnables since currently we cannot instrument them.
|
||||
*
|
||||
* <p>FIXME: We should remove this once https://github.com/raphw/byte-buddy/issues/558 is fixed
|
||||
*/
|
||||
public final class RunnableWrapper implements Runnable {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RunnableWrapper.class);
|
||||
|
||||
public static Runnable wrapIfNeeded(Runnable task) {
|
||||
// We wrap only lambdas' anonymous classes and if given object has not already been wrapped.
|
||||
// Anonymous classes have '/' in class name which is not allowed in 'normal' classes.
|
||||
if (task.getClass().getName().contains("/") && !(task instanceof RunnableWrapper)) {
|
||||
logger.debug("Wrapping runnable task {}", task);
|
||||
return new RunnableWrapper(task);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
private final Runnable runnable;
|
||||
|
||||
private RunnableWrapper(Runnable runnable) {
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ import io.opentelemetry.context.ContextStorage;
|
|||
import io.opentelemetry.context.Scope;
|
||||
import io.opentelemetry.instrumentation.api.config.Config;
|
||||
import io.opentelemetry.javaagent.bootstrap.AgentClassLoader;
|
||||
import io.opentelemetry.javaagent.bootstrap.ClassFileTransformerHolder;
|
||||
import io.opentelemetry.javaagent.extension.AgentExtension;
|
||||
import io.opentelemetry.javaagent.extension.AgentListener;
|
||||
import io.opentelemetry.javaagent.extension.bootstrap.BootstrapPackagesConfigurer;
|
||||
|
@ -143,9 +144,6 @@ public class AgentInstaller {
|
|||
.with(AgentTooling.poolStrategy())
|
||||
.with(new ClassLoadListener())
|
||||
.with(AgentTooling.locationStrategy(Utils.getBootstrapProxy()));
|
||||
// FIXME: we cannot enable it yet due to BB/JVM bug, see
|
||||
// https://github.com/raphw/byte-buddy/issues/558
|
||||
// .with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED)
|
||||
|
||||
agentBuilder = configureIgnoredTypes(config, agentBuilder);
|
||||
|
||||
|
@ -178,6 +176,7 @@ public class AgentInstaller {
|
|||
logger.debug("Installed {} extension(s)", numberOfLoadedExtensions);
|
||||
|
||||
ResettableClassFileTransformer resettableClassFileTransformer = agentBuilder.installOn(inst);
|
||||
ClassFileTransformerHolder.setClassFileTransformer(resettableClassFileTransformer);
|
||||
runAfterAgentListeners(agentListeners, config);
|
||||
return resettableClassFileTransformer;
|
||||
}
|
||||
|
|
|
@ -76,7 +76,9 @@ public class AdditionalLibraryIgnoredTypesConfigurer implements IgnoredTypesConf
|
|||
.ignoreClass("org.springframework.data.")
|
||||
.allowClass("org.springframework.data.repository.core.support.RepositoryFactorySupport")
|
||||
.allowClass("org.springframework.data.convert.ClassGeneratingEntityInstantiator$")
|
||||
.allowClass("org.springframework.data.jpa.repository.config.InspectionClassLoader");
|
||||
.allowClass("org.springframework.data.jpa.repository.config.InspectionClassLoader")
|
||||
.allowClass(
|
||||
"org.springframework.data.jpa.repository.query.QueryParameterSetter$NamedOrIndexedQueryParameterSetter$$Lambda$");
|
||||
|
||||
builder
|
||||
.ignoreClass("org.springframework.amqp.")
|
||||
|
@ -96,7 +98,10 @@ public class AdditionalLibraryIgnoredTypesConfigurer implements IgnoredTypesConf
|
|||
.allowClass("org.springframework.boot.web.servlet.")
|
||||
.allowClass("org.springframework.boot.autoconfigure.BackgroundPreinitializer$")
|
||||
.allowClass("org.springframework.boot.autoconfigure.condition.OnClassCondition$")
|
||||
.allowClass(
|
||||
"org.springframework.boot.autoconfigure.web.ResourceProperties$Cache$Cachecontrol$$Lambda$")
|
||||
.allowClass("org.springframework.boot.web.embedded.netty.NettyWebServer$")
|
||||
.allowClass("org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedContext$$Lambda$")
|
||||
.allowClass(
|
||||
"org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer$")
|
||||
.allowClass(
|
||||
|
@ -127,6 +132,7 @@ public class AdditionalLibraryIgnoredTypesConfigurer implements IgnoredTypesConf
|
|||
// More runnables to deal with
|
||||
.allowClass("org.springframework.context.support.AbstractApplicationContext$")
|
||||
.allowClass("org.springframework.context.support.ContextTypeMatchClassLoader")
|
||||
.allowClass("org.springframework.context.support.DefaultLifecycleProcessor$$Lambda$")
|
||||
// Allow instrumenting ApplicationContext implementations - to inject beans
|
||||
.allowClass("org.springframework.context.annotation.AnnotationConfigApplicationContext")
|
||||
.allowClass("org.springframework.context.support.AbstractApplicationContext")
|
||||
|
@ -222,7 +228,8 @@ public class AdditionalLibraryIgnoredTypesConfigurer implements IgnoredTypesConf
|
|||
builder
|
||||
.ignoreClass("com.google.common.")
|
||||
.allowClass("com.google.common.util.concurrent.")
|
||||
.allowClass("com.google.common.base.internal.Finalizer");
|
||||
.allowClass("com.google.common.base.internal.Finalizer")
|
||||
.allowClass("com.google.common.base.Java8Usage$$Lambda$");
|
||||
|
||||
builder
|
||||
.ignoreClass("com.google.inject.")
|
||||
|
|
|
@ -56,12 +56,7 @@ public class GlobalIgnoredTypesConfigurer implements IgnoredTypesConfigurer {
|
|||
// clojure
|
||||
builder.ignoreClass("clojure.").ignoreClass("$fn__");
|
||||
|
||||
builder
|
||||
.ignoreClass("io.opentelemetry.javaagent.")
|
||||
// FIXME: We should remove this once
|
||||
// https://github.com/raphw/byte-buddy/issues/558 is fixed
|
||||
.allowClass("io.opentelemetry.javaagent.instrumentation.api.concurrent.RunnableWrapper")
|
||||
.allowClass("io.opentelemetry.javaagent.instrumentation.api.concurrent.CallableWrapper");
|
||||
builder.ignoreClass("io.opentelemetry.javaagent.");
|
||||
|
||||
builder
|
||||
.ignoreClass("java.")
|
||||
|
@ -72,6 +67,7 @@ public class GlobalIgnoredTypesConfigurer implements IgnoredTypesConfigurer {
|
|||
.allowClass("java.util.concurrent.")
|
||||
.allowClass("java.lang.reflect.Proxy")
|
||||
.allowClass("java.lang.ClassLoader")
|
||||
.allowClass("java.lang.invoke.InnerClassLambdaMetafactory")
|
||||
// Concurrent instrumentation modifies the structure of
|
||||
// Cleaner class incompatibly with java9+ modules.
|
||||
// Working around until a long-term fix for modules can be
|
||||
|
|
|
@ -33,7 +33,7 @@ public class IgnoredTypesMatcher extends ElementMatcher.Junction.AbstractBase<Ty
|
|||
}
|
||||
|
||||
// bytecode proxies typically have $$ in their name
|
||||
if (name.contains("$$")) {
|
||||
if (name.contains("$$") && !name.contains("$$Lambda$")) {
|
||||
// allow scala anonymous classes
|
||||
return !name.contains("$$anon$");
|
||||
}
|
||||
|
|
|
@ -66,6 +66,7 @@ dependencies {
|
|||
baseJavaagentLibs(project(":instrumentation:executors:javaagent"))
|
||||
baseJavaagentLibs(project(":instrumentation:internal:internal-class-loader:javaagent"))
|
||||
baseJavaagentLibs(project(":instrumentation:internal:internal-eclipse-osgi-3.6:javaagent"))
|
||||
baseJavaagentLibs(project(":instrumentation:internal:internal-lambda:javaagent"))
|
||||
baseJavaagentLibs(project(":instrumentation:internal:internal-proxy:javaagent"))
|
||||
baseJavaagentLibs(project(":instrumentation:internal:internal-reflection:javaagent"))
|
||||
baseJavaagentLibs(project(":instrumentation:internal:internal-url-class-loader:javaagent"))
|
||||
|
|
|
@ -120,6 +120,7 @@ include(":instrumentation:cdi-testing")
|
|||
include(":instrumentation:internal:internal-class-loader:javaagent")
|
||||
include(":instrumentation:internal:internal-class-loader:javaagent-integration-tests")
|
||||
include(":instrumentation:internal:internal-eclipse-osgi-3.6:javaagent")
|
||||
include(":instrumentation:internal:internal-lambda:javaagent")
|
||||
include(":instrumentation:internal:internal-proxy:javaagent")
|
||||
include(":instrumentation:internal:internal-proxy:javaagent-unit-tests")
|
||||
include(":instrumentation:internal:internal-reflection:javaagent")
|
||||
|
|
Loading…
Reference in New Issue