micrometer-metrics / micrometer

An application observability facade for the most popular observability tools. Think SLF4J, but for observability.
https://micrometer.io
Apache License 2.0
4.39k stars 966 forks source link

`ClassNotFoundException` using `Counter` from Micrometer version `1.13.1` #5225

Closed mauricio-sky closed 1 week ago

mauricio-sky commented 2 weeks ago

Context

From the version 1.13.1, using the Counter only, by default, throws an exception on runtime as follow:

Stack trace ``` Exception in thread "main" java.lang.NoClassDefFoundError: io/prometheus/metrics/tracer/initializer/SpanContextSupplier at io.prometheus.metrics.core.exemplars.ExemplarSampler.doSampleExemplar(ExemplarSampler.java:327) at io.prometheus.metrics.core.exemplars.ExemplarSampler.updateExemplar(ExemplarSampler.java:310) at io.prometheus.metrics.core.exemplars.ExemplarSampler.doObserveSingleExemplar(ExemplarSampler.java:123) at io.prometheus.metrics.core.exemplars.ExemplarSampler.doObserve(ExemplarSampler.java:111) at io.prometheus.metrics.core.exemplars.ExemplarSampler.lambda$observe$0(ExemplarSampler.java:99) at io.prometheus.metrics.core.exemplars.ExemplarSampler.rateLimitedObserve(ExemplarSampler.java:276) at io.prometheus.metrics.core.exemplars.ExemplarSampler.observe(ExemplarSampler.java:99) at io.prometheus.metrics.core.metrics.Counter$DataPoint.inc(Counter.java:167) at io.prometheus.metrics.core.metrics.Counter.inc(Counter.java:53) at io.prometheus.metrics.core.datapoints.CounterDataPoint.inc(CounterDataPoint.java:53) at org.example.Main.main(Main.java:7) Caused by: java.lang.ClassNotFoundException: io.prometheus.metrics.tracer.initializer.SpanContextSupplier at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) ... 11 more ```

How to reproduce the issue?

  1. Create a gradle project and includes the dependency:
    implementation("io.micrometer:micrometer-registry-prometheus:1.13.1")
  1. Create a main method with the code below and run it:
import io.prometheus.metrics.core.metrics.Counter;

public class Main {
    public static void main(String[] args) {
        Counter.builder().name("test_metric").build().inc();
    }
}

Alternatives to fix it

The alternatives I found to fix the issue, is 1. setting the Counter without Examples (method .withoutExemplars()), or import the library io.prometheus:metrics.tracer.initializer explicitly.

Investigation notes

jonatan-ivanov commented 1 week ago

You are not using a Micrometer Counter (io.micrometer.core.instrument.Counter) but a Prometheus Counter (io.prometheus.metrics.core.metrics.Counter) and there is nothing in your stacktrace that is from Micrometer. I'm not getting why you use the Micrometer dependency instead of Prometheus.

If you look at the code where this issue is coming from (stacktrace: ExemplarSampler.java:327), you will find this comment:

// Using the qualified name so that Micrometer can exclude the dependency on prometheus-metrics-tracer-initializer
// as they provide their own implementation of SpanContextSupplier.
// If we had an import statement for SpanContextSupplier the dependency would be needed in any case.
SpanContext spanContext = this.spanContext != null ? this.spanContext : io.prometheus.metrics.tracer.initializer.SpanContextSupplier.getSpanContext();

As the comment implies, Micrometer intentionally excludes prometheus-metrics-tracer-initializer. This is because of dependency hygiene: you might not need prometheus-metrics-tracer-otel and prometheus-metrics-tracer-otel-agent that prometheus-metrics-tracer-initializer brings in.

So you need to decide if you want to use the Prometheus Counter in which case you should use the Prometheus dependencies not Micrometer or the Micrometer Counter in which case you should use the Micrometer dependencies. In the case you want to use both (I don't recommend it) either you need to disable exemplars support in Prometheus or add io.prometheus:metrics.tracer.initializer.