I’m trying to create an extension to the Otel Java Agent that will open a span for each method the flow goes through.
My Test Main:
package org.example;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.time.Duration;
/**
* @author David Dvash
*/
public class Main {
public static void main(String[] args) throws InterruptedException {
while (true) {
System.out.println("Going to sleep");
Thread.sleep(10000);
printHello1("Hello");
}
}
public static void printHello1(String name) {
printHello2(name);
}
public static void printHello2(String name) {
System.out.println("Hello, " + name);
}
}
My extension classes:
package org.example;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import java.util.List;
import static java.util.Collections.singletonList;
/**
* @author David Dvash
*/
@AutoService(InstrumentationModule.class)
public class AllMethodsInstrumentationModule extends InstrumentationModule {
public AllMethodsInstrumentationModule() {
super("otel-extension", "otelextension");
System.out.println("In AllMethodsInstrumentationModule Constructor");
}
@Override
public int order() {
return 1;
}
@Override
public List<String> getAdditionalHelperClassNames() {
return List.of(AllMethodsInstrumentation.class.getName(),
"io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation",
MethodSpanAdvice.class.getName());
}
@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
return ElementMatchers.any();
}
@Override
public List<TypeInstrumentation> typeInstrumentations() {
return singletonList(new AllMethodsInstrumentation());
}
}
package org.example;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
/**
* @author David Dvash
*/
@AutoService(TypeInstrumentation.class)
public class AllMethodsInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
System.out.println("in classLoaderOptimization");
// Match all class loaders
return ElementMatchers.any();
}
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
System.out.println("in typeMatcher");
// Match all classes
return ElementMatchers.any();
}
@Override
public void transform(TypeTransformer transformer) {
System.out.println("in transform");
transformer.applyAdviceToMethod(
ElementMatchers.any(),
MethodSpanAdvice.class.getName()
);
}
}
package org.example;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import net.bytebuddy.asm.Advice;
/**
* @author David Dvash
*/
public class MethodSpanAdvice {
// This advice is added at the beginning of the instrumented method (OnMethodEnter).
// It creates and starts a new span, and makes it active.
@Advice.OnMethodEnter(suppress = Throwable.class)
public static Scope onEnter(@Advice.Local("otelSpan") Span span) {
// Get a Tracer instance from OpenTelemetry.
Tracer tracer = GlobalOpenTelemetry.getTracer("instrumentation-library-name", "semver:1.0.0");
System.out.print("Entering method");
// Start a new span with the name "mySpan".
span = tracer.spanBuilder("mySpan").startSpan();
// Make this new span the current active span.
Scope scope = span.makeCurrent();
// Return the Scope instance. This will be used in the exit advice to end the span's scope.
return scope;
}
// This advice is added at the end of the instrumented method (OnMethodExit).
// It first closes the span's scope, then checks if any exception was thrown during the method's execution.
// If an exception was thrown, it sets the span's status to ERROR and ends the span.
// If no exception was thrown, it sets a custom attribute "wordCount" on the span, and ends the span.
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onExit(@Advice.Return(readOnly = false) int wordCount,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelSpan") Span span,
@Advice.Enter Scope scope) {
// Close the scope to end it.
scope.close();
// If an exception was thrown during the method's execution, set the span's status to ERROR.
if (throwable != null) {
span.setStatus(StatusCode.ERROR, "Exception thrown in method");
} else {
// If no exception was thrown, set a custom attribute "wordCount" on the span.
span.setAttribute("wordCount", wordCount);
}
// End the span. This makes it ready to be exported to the configured exporter (e.g., Jaeger, Zipkin).
span.end();
}
}
The extension’s gradle.build:
plugins {
id 'java'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
implementation group: 'net.bytebuddy', name: 'byte-buddy', version: '1.15.3'
implementation group: 'io.opentelemetry', name: 'opentelemetry-api', version: '1.42.1'
implementation 'io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api:2.8.0-alpha'
// https://mvnrepository.com/artifact/io.opentelemetry.javaagent/opentelemetry-javaagent-tooling
implementation 'io.opentelemetry.javaagent:opentelemetry-javaagent-tooling:2.8.0-alpha'
// https://mvnrepository.com/artifact/com.google.auto.service/auto-service
implementation 'com.google.auto.service:auto-service:1.1.1
}
test {
useJUnitPlatform()
}
The VM arguments I use in IntelliJ to run the agent and the extension: -javaagent:"F:\\views\\g\main_mono_new\\Qrelease\\3rd\\openTelemetry-javaAgent\\opentelemetry-javaagent.jar" -Dotel.javaagent.extensions="F:\\views\\OtelExtension\\build\\libs\\OtelExtension-1.0-SNAPSHOT.jar" -Dotel.service.name=test-service -Dotel.javaagent.debug=true
I know the agent works correctly because I added some manual traces and were able to see them In Grafana (using Tempo). However I don’t see any of the traces I expect to see from the extension. What is not correct with my extension?
You need to sign in to view this answers
Leave feedback about this