Skip to content
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 Expand Up @@ -144,6 +145,7 @@ dependencies {
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation("io.micrometer:micrometer-observation-test")
testImplementation("io.projectreactor:reactor-test")
testImplementation("io.prometheus:prometheus-metrics-exposition-formats")
testImplementation("io.r2dbc:r2dbc-h2")
testImplementation("com.squareup.okhttp3:mockwebserver")
testImplementation("com.jayway.jsonpath:json-path")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,39 +16,28 @@

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 io.prometheus.metrics.tracer.common.SpanContext;

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 +47,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, ObjectProvider<SpanContext> spanContext) {
return new PrometheusMeterRegistry(prometheusConfig, prometheusRegistry, clock, spanContext.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);
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,7 +54,13 @@ public class PrometheusProperties {
/**
* Histogram type for backing DistributionSummary and Timer.
*/
private io.micrometer.prometheus.HistogramFlavor histogramFlavor = io.micrometer.prometheus.HistogramFlavor.Prometheus;
@Deprecated(since = "3.3.0")
private HistogramFlavor histogramFlavor = HistogramFlavor.Prometheus;

/**
* Additional properties to pass to the Prometheus client.
*/
private final Map<String, String> prometheusProperties = new HashMap<>();

/**
* Step size (i.e. reporting frequency) to use.
Expand All @@ -70,11 +75,11 @@ public void setDescriptions(boolean descriptions) {
this.descriptions = descriptions;
}

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.

public HistogramFlavor getHistogramFlavor() {
return this.histogramFlavor;
}

public void setHistogramFlavor(io.micrometer.prometheus.HistogramFlavor histogramFlavor) {
public void setHistogramFlavor(HistogramFlavor histogramFlavor) {
this.histogramFlavor = histogramFlavor;
}

Expand All @@ -98,6 +103,10 @@ public Pushgateway getPushgateway() {
return this.pushgateway;
}

public Map<String, String> getPrometheusProperties() {
return this.prometheusProperties;
}

/**
* Configuration options for push-based interaction with Prometheus.
*/
Expand Down Expand Up @@ -209,4 +218,13 @@ public void setShutdownOperation(ShutdownOperation shutdownOperation) {

}

public enum HistogramFlavor {

Prometheus, VictoriaMetrics;

HistogramFlavor() {
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,21 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus;

import java.time.Duration;
import java.util.Map;
import java.util.Properties;

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 +49,28 @@ public String get(String key) {

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

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

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

private Properties fromPropertiesMap(PrometheusProperties prometheusProperties) {
Map<String, String> map = prometheusProperties.getPrometheusProperties();
if (map.isEmpty()) {
return null;
}
Properties properties = PrometheusConfig.super.prometheusProperties();
properties = (properties != null) ? properties : new Properties();
properties.putAll(map);
return properties;
}

}
Loading