I want to implement extension to manage methods tracing. I've implemented TypeInstrumentation class to around and utilite class for instrumenter() as described opentelemetry instrumentation module:
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndSupport;
import io.opentelemetry.instrumentation.api.instrumenter.util.ClassAndMethod;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.matcher.ElementMatcher;
import java.lang.reflect.Method;
import java.util.Set;
import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
import static org.telemetry.extensions.MethodSingletons.instrumenter;
public class MethodInstrumentation implements TypeInstrumentation {
private final String className;
private final Set<String> methodNames;
public MethodInstrumentation(String className, Set<String> methodNames) {
this.className = className;
this.methodNames = methodNames;
}
@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
return hasClassesNamed(className);
}
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return hasSuperType(named(className));
}
@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
namedOneOf(methodNames.toArray(new String[0])),
OpenLMethodInstrumentation.class.getName() + "$MethodAdvice");
}
@SuppressWarnings("unused")
public static class MethodAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Origin("#t") Class<?> declaringClass,
@Advice.Origin("#m") String methodName,
@Advice.Local("otelMethod") ClassAndMethod classAndMethod,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
Context parentContext = currentContext();
classAndMethod = ClassAndMethod.create(declaringClass, methodName);
try {
context = instrumenter().start(parentContext, classAndMethod);
} catch (Throwable e) {
System.out.println("SOme error happened!");
System.out.println(e.getClass());
System.out.println(e.getMessage());
throw e;
}
scope = context.makeCurrent();
}
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Origin Method method,
@Advice.Local("otelMethod") ClassAndMethod classAndMethod,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope,
@Advice.Return(typing = Assigner.Typing.DYNAMIC, readOnly = false) Object returnValue,
@Advice.Thrown Throwable throwable) {
scope.close();
returnValue =
AsyncOperationEndSupport.create(instrumenter(), Void.class, method.getReturnType())
.asyncEnd(context, classAndMethod, returnValue, throwable);
}
}
}
MethodInstrumenter utility class with instrumenter:
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesGetter;
import io.opentelemetry.instrumentation.api.instrumenter.code.CodeSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.util.ClassAndMethod;
public final class MethodSingletons {
public MethodSingletons() {
}
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.methods";
private static final Instrumenter<ClassAndMethod, Void> INSTRUMENTER;
static {
CodeAttributesGetter<ClassAndMethod> codeAttributesGetter =
ClassAndMethod.codeAttributesGetter();
INSTRUMENTER =
Instrumenter.<ClassAndMethod, Void>builder(
GlobalOpenTelemetry.get(),
INSTRUMENTATION_NAME,
CodeSpanNameExtractor.create(codeAttributesGetter))
.addAttributesExtractor(CodeAttributesExtractor.create(codeAttributesGetter))
.buildInstrumenter(SpanKindExtractor.alwaysInternal());
}
public static Instrumenter<ClassAndMethod, Void> instrumenter() {
return INSTRUMENTER;
}
}
After I build this code I'm adding it as extension to javaagent configuration:
-javaagent:/Users/..../target/opentelemetry-javaagent.jar
-Dotel.javaagent.extensions=/Users/..../target/org.tracing.opentelemetry-1.0.0-SNAPSHOT.jar
And get NoClassDefFoundError in instrumenter().start
line:
class java.lang.NoClassDefFoundError
Could not initialize class org....telemetry.extensions.MethodSingletons
What is wrong? How I can configure instrumentation in opentelemetry extension?
UPD: I found that seems NoClassDefFoundError is related to CodeAttributesExtractor class in MethodInstrumentation class
UPD2: initial InstrumentationModule class:
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.List;
import java.util.Set;
@AutoService(InstrumentationModule.class)
public class MethodsInstrumentationModule extends InstrumentationModule {
public MethodsInstrumentationModule() {
super("some-methods");
}
@Override
public List<TypeInstrumentation> typeInstrumentations() {
return List.of(
new TypeInstrumentation("org.some.package.Some1",
Set.of("someMethod"))
);
}
}
It is required to inject helper classes as @Rafael Winterhalter mentioned. I found out how to do it. It is required to redefine getAdditionalHelperClassNames()
method in InstrumentationModule class, as described here. So I added the next method to MethodsInstrumentationModule class and all works fine:
@Override
public List<String> getAdditionalHelperClassNames() {
return List.of(MethodSingletons.class.getName());
}