OiO.lk Blog java Why Open Telemetry Java Agent Extension doesn't work
java

Why Open Telemetry Java Agent Extension doesn't work


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

Exit mobile version