Skip to content

Add support for Prometheus Client 1.x and simpleclient #40023

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ dependencies {
optional("io.micrometer:micrometer-registry-kairos")
optional("io.micrometer:micrometer-registry-new-relic")
optional("io.micrometer:micrometer-registry-otlp")
optional("io.micrometer:micrometer-registry-prometheus")
optional("io.micrometer:micrometer-registry-prometheus-simpleclient")
optional("io.micrometer:micrometer-registry-stackdriver") {
exclude group: "commons-logging", module: "commons-logging"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,39 +16,26 @@

package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus;

import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.util.Map;

import io.micrometer.core.instrument.Clock;
import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.exemplars.DefaultExemplarSampler;
import io.prometheus.client.exemplars.ExemplarSampler;
import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier;
import io.prometheus.client.exporter.BasicAuthHttpConnectionFactory;
import io.prometheus.client.exporter.PushGateway;
import io.micrometer.prometheusmetrics.PrometheusConfig;
import io.micrometer.prometheusmetrics.PrometheusMeterRegistry;
import io.prometheus.metrics.model.registry.PrometheusRegistry;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.ConditionalOnEnabledMetricsExport;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager;
import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager.ShutdownOperation;
import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusScrapeEndpoint;
import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusSimpleclientScrapeEndpoint;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;

/**
* {@link EnableAutoConfiguration Auto-configuration} for exporting metrics to Prometheus.
Expand All @@ -58,98 +45,43 @@
* @author Jonatan Ivanov
* @since 2.0.0
*/
@SuppressWarnings("deprecation")
@AutoConfiguration(
before = { CompositeMeterRegistryAutoConfiguration.class, SimpleMetricsExportAutoConfiguration.class },
after = MetricsAutoConfiguration.class)
@ConditionalOnBean(Clock.class)
@ConditionalOnClass(io.micrometer.prometheus.PrometheusMeterRegistry.class)
@ConditionalOnClass(PrometheusMeterRegistry.class)
@ConditionalOnEnabledMetricsExport("prometheus")
@EnableConfigurationProperties(PrometheusProperties.class)
public class PrometheusMetricsExportAutoConfiguration {

@Bean
@ConditionalOnMissingBean
public io.micrometer.prometheus.PrometheusConfig prometheusConfig(PrometheusProperties prometheusProperties) {
public PrometheusConfig prometheusConfig(PrometheusProperties prometheusProperties) {
return new PrometheusPropertiesConfigAdapter(prometheusProperties);
}

@Bean
@ConditionalOnMissingBean
public io.micrometer.prometheus.PrometheusMeterRegistry prometheusMeterRegistry(
io.micrometer.prometheus.PrometheusConfig prometheusConfig, CollectorRegistry collectorRegistry,
Clock clock, ObjectProvider<ExemplarSampler> exemplarSamplerProvider) {
return new io.micrometer.prometheus.PrometheusMeterRegistry(prometheusConfig, collectorRegistry, clock,
exemplarSamplerProvider.getIfAvailable());
public PrometheusMeterRegistry prometheusMeterRegistry(PrometheusConfig prometheusConfig,
PrometheusRegistry prometheusRegistry, Clock clock) {
return new PrometheusMeterRegistry(prometheusConfig, prometheusRegistry, clock, null);
}

@Bean
@ConditionalOnMissingBean
public CollectorRegistry collectorRegistry() {
return new CollectorRegistry(true);
}

@Bean
@ConditionalOnMissingBean(ExemplarSampler.class)
@ConditionalOnBean(SpanContextSupplier.class)
public DefaultExemplarSampler exemplarSampler(SpanContextSupplier spanContextSupplier) {
return new DefaultExemplarSampler(spanContextSupplier);
public PrometheusRegistry prometheusRegistry() {
return new PrometheusRegistry();
}

@Configuration(proxyBeanMethods = false)
@ConditionalOnAvailableEndpoint(endpoint = PrometheusScrapeEndpoint.class)
public static class PrometheusScrapeEndpointConfiguration {

@SuppressWarnings("removal")
@Bean
@ConditionalOnMissingBean
public PrometheusScrapeEndpoint prometheusEndpoint(CollectorRegistry collectorRegistry) {
return new PrometheusScrapeEndpoint(collectorRegistry);
}

}

/**
* Configuration for <a href="https://github.com/prometheus/pushgateway">Prometheus
* Pushgateway</a>.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(PushGateway.class)
@ConditionalOnProperty(prefix = "management.prometheus.metrics.export.pushgateway", name = "enabled")
public static class PrometheusPushGatewayConfiguration {

/**
* The fallback job name. We use 'spring' since there's a history of Prometheus
* spring integration defaulting to that name from when Prometheus integration
* didn't exist in Spring itself.
*/
private static final String FALLBACK_JOB = "spring";

@Bean
@ConditionalOnMissingBean
public PrometheusPushGatewayManager prometheusPushGatewayManager(CollectorRegistry collectorRegistry,
PrometheusProperties prometheusProperties, Environment environment) throws MalformedURLException {
PrometheusProperties.Pushgateway properties = prometheusProperties.getPushgateway();
Duration pushRate = properties.getPushRate();
String job = getJob(properties, environment);
Map<String, String> groupingKey = properties.getGroupingKey();
ShutdownOperation shutdownOperation = properties.getShutdownOperation();
PushGateway pushGateway = initializePushGateway(properties.getBaseUrl());
if (StringUtils.hasText(properties.getUsername())) {
pushGateway.setConnectionFactory(
new BasicAuthHttpConnectionFactory(properties.getUsername(), properties.getPassword()));
}
return new PrometheusPushGatewayManager(pushGateway, collectorRegistry, pushRate, job, groupingKey,
shutdownOperation);
}

private PushGateway initializePushGateway(String url) throws MalformedURLException {
return new PushGateway(new URL(url));
}

private String getJob(PrometheusProperties.Pushgateway properties, Environment environment) {
String job = properties.getJob();
job = (job != null) ? job : environment.getProperty("spring.application.name");
return (job != null) ? job : FALLBACK_JOB;
@ConditionalOnMissingBean({ PrometheusScrapeEndpoint.class, PrometheusSimpleclientScrapeEndpoint.class })
public PrometheusScrapeEndpoint prometheusEndpoint(PrometheusRegistry prometheusRegistry) {
return new PrometheusScrapeEndpoint(prometheusRegistry);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
* @author Stephane Nicoll
* @since 2.0.0
*/
@SuppressWarnings("deprecation")
@ConfigurationProperties(prefix = "management.prometheus.metrics.export")
public class PrometheusProperties {

Expand All @@ -55,6 +54,8 @@ public class PrometheusProperties {
/**
* Histogram type for backing DistributionSummary and Timer.
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated(since = "3.3.0", forRemoval = true)
private io.micrometer.prometheus.HistogramFlavor histogramFlavor = io.micrometer.prometheus.HistogramFlavor.Prometheus;

/**
Expand All @@ -70,10 +71,12 @@ public void setDescriptions(boolean descriptions) {
this.descriptions = descriptions;
}

@SuppressWarnings("deprecation")
public io.micrometer.prometheus.HistogramFlavor getHistogramFlavor() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this from here to avoid the NoClassDefFoundError Moritz found before. I've duplicated the class similar to other classes so there is a simpleclient version now that is used by the corresponding autoconfig. I also removed the Pushgateway related properties until the latest versions of the Prometheus client support it and we can add back auto-configuration support for it here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reverted the change duplicating the ConfigurationProperties class and instead made a HistogramFlavor enum here to avoid requiring the micrometer-registry-prometheus-simpleclient module. The config adapter handles mapping to the Micrometer enum.

return this.histogramFlavor;
}

@SuppressWarnings("deprecation")
public void setHistogramFlavor(io.micrometer.prometheus.HistogramFlavor histogramFlavor) {
this.histogramFlavor = histogramFlavor;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@

import java.time.Duration;

import io.micrometer.prometheusmetrics.PrometheusConfig;

import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.PropertiesConfigAdapter;

/**
* Adapter to convert {@link PrometheusProperties} to a
* {@link io.micrometer.prometheus.PrometheusConfig}.
* Adapter to convert {@link PrometheusProperties} to a {@link PrometheusConfig}.
*
* @author Jon Schneider
* @author Phillip Webb
*/
@SuppressWarnings("deprecation")
class PrometheusPropertiesConfigAdapter extends PropertiesConfigAdapter<PrometheusProperties>
implements io.micrometer.prometheus.PrometheusConfig {
implements PrometheusConfig {

PrometheusPropertiesConfigAdapter(PrometheusProperties properties) {
super(properties);
Expand All @@ -47,18 +47,12 @@ public String get(String key) {

@Override
public boolean descriptions() {
return get(PrometheusProperties::isDescriptions, io.micrometer.prometheus.PrometheusConfig.super::descriptions);
}

@Override
public io.micrometer.prometheus.HistogramFlavor histogramFlavor() {
return get(PrometheusProperties::getHistogramFlavor,
io.micrometer.prometheus.PrometheusConfig.super::histogramFlavor);
return get(PrometheusProperties::isDescriptions, PrometheusConfig.super::descriptions);
}

@Override
public Duration step() {
return get(PrometheusProperties::getStep, io.micrometer.prometheus.PrometheusConfig.super::step);
return get(PrometheusProperties::getStep, PrometheusConfig.super::step);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus;

import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.util.Map;

import io.micrometer.core.instrument.Clock;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.exemplars.DefaultExemplarSampler;
import io.prometheus.client.exemplars.ExemplarSampler;
import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier;
import io.prometheus.client.exporter.BasicAuthHttpConnectionFactory;
import io.prometheus.client.exporter.PushGateway;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.ConditionalOnEnabledMetricsExport;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager;
import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager.ShutdownOperation;
import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusScrapeEndpoint;
import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusSimpleclientScrapeEndpoint;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;

/**
* {@link EnableAutoConfiguration Auto-configuration} for exporting metrics to Prometheus
* with the Prometheus simpleclient.
*
* @author Jon Schneider
* @author David J. M. Karlsen
* @author Jonatan Ivanov
* @since 2.0.0
* @deprecated in favor of {@link PrometheusMetricsExportAutoConfiguration}
*/
@Deprecated(since = "3.3.0", forRemoval = true)
@AutoConfiguration(
before = { CompositeMeterRegistryAutoConfiguration.class, SimpleMetricsExportAutoConfiguration.class },
after = { MetricsAutoConfiguration.class, PrometheusMetricsExportAutoConfiguration.class })
@ConditionalOnBean(Clock.class)
@ConditionalOnClass(PrometheusMeterRegistry.class)
@ConditionalOnEnabledMetricsExport("prometheus")
@EnableConfigurationProperties(PrometheusProperties.class)
public class PrometheusSimpleclientMetricsExportAutoConfiguration {

@Bean
@ConditionalOnMissingBean
public PrometheusConfig simpleclientPrometheusConfig(PrometheusProperties prometheusProperties) {
return new PrometheusSimpleclientPropertiesConfigAdapter(prometheusProperties);
}

@Bean
@ConditionalOnMissingBean
public io.micrometer.prometheus.PrometheusMeterRegistry simpleclientPrometheusMeterRegistry(
io.micrometer.prometheus.PrometheusConfig prometheusConfig, CollectorRegistry collectorRegistry,
Clock clock, ObjectProvider<ExemplarSampler> exemplarSamplerProvider) {
return new io.micrometer.prometheus.PrometheusMeterRegistry(prometheusConfig, collectorRegistry, clock,
exemplarSamplerProvider.getIfAvailable());
}

@Bean
@ConditionalOnMissingBean
public CollectorRegistry collectorRegistry() {
return new CollectorRegistry(true);
}

@Bean
@ConditionalOnMissingBean(ExemplarSampler.class)
@ConditionalOnBean(SpanContextSupplier.class)
public DefaultExemplarSampler exemplarSampler(SpanContextSupplier spanContextSupplier) {
return new DefaultExemplarSampler(spanContextSupplier);
}

@SuppressWarnings("removal")
@Configuration(proxyBeanMethods = false)
@ConditionalOnAvailableEndpoint(endpoint = PrometheusSimpleclientScrapeEndpoint.class)
public static class PrometheusScrapeEndpointConfiguration {

@Bean
@ConditionalOnMissingBean({ PrometheusSimpleclientScrapeEndpoint.class, PrometheusScrapeEndpoint.class })
public PrometheusSimpleclientScrapeEndpoint prometheusEndpoint(CollectorRegistry collectorRegistry) {
return new PrometheusSimpleclientScrapeEndpoint(collectorRegistry);
}

}

/**
* Configuration for <a href="https://github.com/prometheus/pushgateway">Prometheus
* Pushgateway</a>.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(PushGateway.class)
@ConditionalOnProperty(prefix = "management.prometheus.metrics.export.pushgateway", name = "enabled")
public static class PrometheusPushGatewayConfiguration {

/**
* The fallback job name. We use 'spring' since there's a history of Prometheus
* spring integration defaulting to that name from when Prometheus integration
* didn't exist in Spring itself.
*/
private static final String FALLBACK_JOB = "spring";

@Bean
@ConditionalOnMissingBean
public PrometheusPushGatewayManager prometheusPushGatewayManager(CollectorRegistry collectorRegistry,
PrometheusProperties prometheusProperties, Environment environment) throws MalformedURLException {
PrometheusProperties.Pushgateway properties = prometheusProperties.getPushgateway();
Duration pushRate = properties.getPushRate();
String job = getJob(properties, environment);
Map<String, String> groupingKey = properties.getGroupingKey();
ShutdownOperation shutdownOperation = properties.getShutdownOperation();
PushGateway pushGateway = initializePushGateway(properties.getBaseUrl());
if (StringUtils.hasText(properties.getUsername())) {
pushGateway.setConnectionFactory(
new BasicAuthHttpConnectionFactory(properties.getUsername(), properties.getPassword()));
}
return new PrometheusPushGatewayManager(pushGateway, collectorRegistry, pushRate, job, groupingKey,
shutdownOperation);
}

private PushGateway initializePushGateway(String url) throws MalformedURLException {
return new PushGateway(new URL(url));
}

private String getJob(PrometheusProperties.Pushgateway properties, Environment environment) {
String job = properties.getJob();
job = (job != null) ? job : environment.getProperty("spring.application.name");
return (job != null) ? job : FALLBACK_JOB;
}

}

}
Loading