Disable exemplars support in Spring Boot 3.2 to avoid Prometheus problem with scrapping metrics
Asked Answered
E

4

8

New Spring Boot added exemplars support: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.2-Release-Notes#broader-exemplar-support-in-micrometer-112

After upgrading to Spring Boot 3.2.0 Prometheus stopped scrapping a metrics from our apps, because it requires a Prometheus version 2.43.0 or newer:

metric name web_client_requests_seconds_count does not support exemplars

I think there should be a way to disable this until we won't upgrade a Prometheus.

Ephesus answered 1/12, 2023 at 16:3 Comment(0)
W
8

I think this is because you are using an unsupported version of Prometheus server. You will need this change in Prometheus: Exemplars support for all time series #11982. It was introduced in Prometheus 2.43.0. Also see discussion about this: Add exemplar support to _count #3996.

According to the Prometheus support terms, you should use at least 2.45.

If you really want to hack this (please don't and use a supported version of Prometheus instead), I think if you create a SpanContextSupplier @Bean that always says isSampled false and it can return null for the spanId and traceId, you will not see those exemplars.

It is also possible to modify the value of the Accept header and use text/plain instead of application/openmetrics-text in a custom controller or in a gateway or reverse-proxy. This should work because the OpenMetrics format supports exemplars while the Prometheus format does not (Prometheus asks for the OpenMetrics format by default).

Update

In Spring Boot 3.3.0 and Micrometer 1.13.0, we added support to the Prometheus 1.x Java Client which supports conditionally enabling exemplars on all time series (i.e.: _count), see PrometheusConfig in Micrometer.

Welles answered 1/12, 2023 at 23:27 Comment(13)
Thanks for your answer. The thing is that our Prometheus will be upgraded in a month or two and we can't release our Spring Boot apps, because metrics are broken.Ephesus
I get that but right now you are using an older and unsupported version of Prometheus. If the Prometheus version would be supported by the Prometheus team, we would have added a config flag but since it is not supported anymore, we did not. I provided two options, I guess another workaround could be creating your own controller endpoint.Welles
@JonatanIvanov Metrics is an Enterprise kind of feature. Not even attempting backwards compatibility when 2.43 was just released in Mar 21, 2023 is ridiculous. Big corporations use Spring Boot, and sometimes infrastructure moves much slower than individual services ever could. Ludicrous is what all this is.Amherst
>Metrics is an Enterprise kind of feature. You can use metrics anywhere even in your tests. >Not even attempting backwards compatibility when 2.43 was just released in Mar 21, 2023 is ridiculous. It is backward compatible. Newer versions of Prometheus are still able to parse older formats, forward compatibility is the issue but I think Prometheus does not worry too much about it. >Big corporations use Spring Boot, and sometimes infrastructure moves much slower than individual services ever could. You can open a new issue for Boot but why should Boot support unsupported versions?Welles
@JonatanIvanov how could I change from application/openmetrics-text to text/plain??Reality
@Reality I edited my answer, I remembered that you can set auth headers with Prometheus but it seems that's the only header Prometheus supports. If you want to change the headers, you need a reverse-proxy or gateway to do it. See: github.com/prometheus/prometheus/issues/1724 and #66032998Welles
@JonatanIvanov The problem with this "unsupported version of Prometheus" statement is that it greatly depends on who declares it as "unsupported". I understand that Prometheus itself is the 1st party here. But I think the developers of tools around it should also take into account that many users use 3rd party solutions that use Prometheus under the hood. And they usually declare versions as "unsupported" much slower, even worse it is out of control of Micrometer users. Such example is Google Cloud where Managed Prometheus is still 2.41 and it will stay 2.41 for some months from now.Fighterbomber
@RuslanStelmachenko That's a good point, do you know if these 3rd party vendors have support terms around their hosted Prometheus? If no, I think what I wrote should be still applicable (since it is still not supported and not updated by the vendors). If they do and they also issue releases (bugfixes, cves, etc), disabling this is a valid issue. You can do it by creating a single bean (see above). If you want a property, please create an issue in Spring Boot.Welles
@JonatanIvanov I'm not sure about their support terms from the top of my head, but they definitely have support :) Please see this issue for example. The problem with big platforms like GKE is that they are very inert in terms of updates. Every updates goes through a long process of testing through several release channels, so a fix implemented today could be useful by production GKE cluster only after 6 months.Thanks for the suggestion,will doFighterbomber
Fyi: I updated the answer, the new Prometheus Java Client is capable of conditionally enable/disable exemplars on all time series (i.e.: _count) so with the new Micrometer release you can turn those off.Welles
@JonatanIvanov Can you provide an example of disabling exemplars for spring boot 3.3?Facing
@JonatanIvanov Or how can I customize PrometheusConfig for the spring boot configuration?Facing
See the docs: docs.micrometer.io/micrometer/reference/implementations/… docs.spring.io/spring-boot/appendix/application-properties/…Welles
F
3

Another variant of solving the problem is overriding the ExemplarSampler bean provided by Spring Boot auto-configuration with an implementation that disables exemplars only for counter metrics (histogram metrics will still have exemplars attached).

There are 2 simple ways of doing this.

  1. You can define ExemplarSampler bean, that will substitue the default DefaultExemplarSampler implementation. The implementation could just delegate calls to all methods except io.prometheus.client.exemplars.CounterExemplarSampler.sample to the DefaultExemplarSampler instance, and CounterExemplarSampler.sample should just return null.

  2. You can define a BeanPostProcessor, that does basically the same, but does not rely on the DefaultExemplarSampler implementation. It will just wrap any found ExemplarSampler bean into the new implementation.

An example of such BeanPostProcessor:

/**
 * A {@link BeanPostProcessor} that wraps any {@link ExemplarSampler} bean with an implementation
 * that delegates all method calls to the wrapped bean, except
 * {@link io.prometheus.client.exemplars.CounterExemplarSampler#sample(double, Exemplar)}.
 * This have an effect of disabled Exemplars for counter metrics, introduced in Prometheus 2.43.
 * Any counter metrics with exemplars are ignored by any older Prometheus version on scrapping,
 * so it is important for counter metrics to <b>not</b> have exemplars, or they will be ignored,
 * if scrapped by Prometheus version less than 2.43.
 *
 * @see <a href="https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.2-Release-Notes#broader-exemplar-support-in-micrometer-112">Spring Boot 3.2 Release Notes</a>
 * @see <a href="https://github.com/GoogleCloudPlatform/prometheus-engine/issues/812">Managed Prometheus on GCP Issue</a>
 * @see <a href="https://mcmap.net/q/1284966/-disable-exemplars-support-in-spring-boot-3-2-to-avoid-prometheus-problem-with-scrapping-metrics">Stackoverflow discussion</a>
 * @see <a href="https://github.com/micrometer-metrics/micrometer/pull/3996">Micrometer Metrics PR</a>
 */
@Component
public class ExemplarSamplerBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof ExemplarSampler delegate) {
            return new IgnoreCountersExemplarSampler(delegate);
        }
        return bean;
    }

    @RequiredArgsConstructor
    static class IgnoreCountersExemplarSampler implements ExemplarSampler {

        private final ExemplarSampler delegate;

        @Override
        public Exemplar sample(double increment, Exemplar previous) {
            // Do not return exemplar for counter metrics to allow them to be scrapped by Prometheus 2.42 and below
            return null;
        }

        @Override
        public Exemplar sample(double value, double bucketFrom, double bucketTo, Exemplar previous) {
            return delegate.sample(value, bucketFrom, bucketTo, previous);
        }

    }

}

Put this component into a component-scanned package and that's all. Counter metrics will not include exemplars anymore and Prometheus older than 2.43 will successfully scrap them.

If you do not want to use BeanPostProcessor, here is the solution with @Bean override:

@Configuration
@ConditionalOnEnabledMetricsExport("prometheus")
public class PrometheusExemplarsConfig {

    @Bean
    IgnoreCountersExemplarSampler exemplarSampler(SpanContextSupplier spanContextSupplier) {
        ExemplarSampler delegate = new DefaultExemplarSampler(spanContextSupplier);
        return new IgnoreCountersExemplarSampler(delegate);
    }

}

The implementation of IgnoreCountersExemplarSampler is the same.

Fighterbomber answered 26/2 at 17:12 Comment(0)
L
2

Jonatan Ivanov wrote good suggestions. I just want to publish a piece of code that makes it possible to return Prometheus metrics in the text/plain format:

@Component
@WebEndpoint(id = "prometheus-text")
@RequiredArgsConstructor
@ConditionalOnAvailableEndpoint(endpoint = PrometheusPlainScrapeEndpoint.class)
@ConditionalOnEnabledMetricsExport("prometheus")
public class PrometheusPlainScrapeEndpoint {

    private final PrometheusScrapeEndpoint prometheusScrapeEndpoint;

    @ReadOperation(produces = "text/plain")
    public WebEndpointResponse<String> scrape(@Nullable Set<String> includedNames) {
        return prometheusScrapeEndpoint.scrape(TextOutputFormat.CONTENT_TYPE_004, includedNames);
    }

}

Then add prometheus-text in management.endpoints.web.exposure.include property of application.properties (or yaml) file. It would be like management.endpoints.web.exposure.include=healt,prometheus,prometheus-text or just management.endpoints.web.exposure.include=*.

After all changes don't forget to change the scrape endpoint in your Prometheus config file to /actuator/prometheus-text.

Lordship answered 14/12, 2023 at 13:52 Comment(0)
V
0

I fixed it by adding this line in your application.properties or application.yml. It's the update that Jonatan mentioned in his answer HERE

management.prometheus.metrics.export.properties.io.prometheus.exporter.exemplarsOnAllMetricTypes= false
Verminous answered 12/9 at 15:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.