I am trying to follow the open telemetry auto instrumentation java agent extension and I have come up with the following instrumentation code:
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package com.example.javaagent.instrumentation;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.ObservableLongMeasurement;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
public class DemoHelloWorldInstrumentation implements TypeInstrumentation {
private static String CLASS_NAME = "my.package.MyClass";
private static OpenTelemetry telemetry = GlobalOpenTelemetry.get();
private static Meter meter = telemetry.getMeterProvider().meterBuilder(CLASS_NAME).build();
private static ObservableLongMeasurement click =
meter
.counterBuilder("click")
.setUnit("counter")
.setDescription("Number of click")
.buildObserver();
;
static {
meter.batchCallback(
() -> {
click.record(1, Attributes.of(stringKey("none"), "none"));
},
click);
}
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return AgentElementMatchers.hasSuperType(namedOneOf(CLASS_NAME));
}
@Override
public void transform(TypeTransformer typeTransformer) {
typeTransformer.applyAdviceToMethod(
namedOneOf("sayHello"), this.getClass().getName() + "$GreetingAdvice");
}
@SuppressWarnings("unused")
public static class GreetingAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(@Advice.Argument(value = 0) String name) {
System.out.println("inst name = " + name);
click.record(1, Attributes.of(stringKey("name"), name));
}
}
}
I am expecting some metrics would be exported with name = click
but neither the metrics get exported nor println
output anything in stdout. I have checked and made sure the namings are correct. It seems to build ok inside the open telemetry extension project and I could reference it using the -Dotel.javaagent.extensions=/path/to/my/extension.jar
to run the app , when I hit the endpoint it seems to work and other metrics are exported. Anyone got any suggestion on how to debug this or if you know what I have done wrong pls let me know thank you!!
Have you tried using the javaagent debug mode? It's very verbose, but should print some useful output.
You should not put any state or dependencies into the TypeInstrumentation
class (neither into the advice class). In the OTel javaagent, the agent code is inaccessible from the application code; all code that is supposed to be injected into the application code must be kept in separate classes. The telemetry
, meter
, click
fields that you have in your TypeInstrumentation
implementation are simply not visible from your advice class. I'd advice extracting all logic to a separate helper class, and just call that class from the advice.
See https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/contributing/writing-instrumentation-module.md#use-advice-classes-to-write-code-that-will-get-injected-to-the-instrumented-library-classes for a more detailed explanation on how to correctly implement an InstrumentationModule
, TypeInstrumentation
s and advice classes.
Other than that, the buildObserver()
and ObservableLongMeasurement
are only supposed to be used within a BatchCallback
. It will not register anything if you call it directly in the advice class; use build()
and LongCounter
instead.