diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle
index a4fafba13d2..a07c2d7d41c 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle
@@ -54,9 +54,6 @@ dependencies {
exclude group: "commons-logging", module: "commons-logging"
exclude group: "javax.annotation", module: "javax.annotation-api"
}
- optional("io.prometheus:simpleclient_pushgateway") {
- exclude group: "javax.xml.bind", module: "jaxb-api"
- }
optional("io.micrometer:micrometer-registry-signalfx")
optional("io.micrometer:micrometer-registry-statsd")
optional("io.micrometer:micrometer-registry-wavefront")
@@ -65,6 +62,7 @@ dependencies {
optional("io.opentelemetry:opentelemetry-exporter-zipkin")
optional("io.opentelemetry:opentelemetry-exporter-otlp")
optional("io.projectreactor.netty:reactor-netty-http")
+ optional("io.prometheus:prometheus-metrics-exporter-pushgateway")
optional("io.r2dbc:r2dbc-pool")
optional("io.r2dbc:r2dbc-proxy")
optional("io.r2dbc:r2dbc-spi")
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfiguration.java
index 2e79d61f8f0..5717c2db1ab 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfiguration.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfiguration.java
@@ -19,6 +19,10 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus
import io.micrometer.core.instrument.Clock;
import io.micrometer.prometheusmetrics.PrometheusConfig;
import io.micrometer.prometheusmetrics.PrometheusMeterRegistry;
+import io.prometheus.metrics.exporter.pushgateway.Format;
+import io.prometheus.metrics.exporter.pushgateway.PushGateway;
+import io.prometheus.metrics.exporter.pushgateway.PushGateway.Builder;
+import io.prometheus.metrics.exporter.pushgateway.Scheme;
import io.prometheus.metrics.model.registry.PrometheusRegistry;
import io.prometheus.metrics.tracer.common.SpanContext;
@@ -28,15 +32,20 @@ import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegi
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.PrometheusScrapeEndpoint;
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.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException;
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.
@@ -87,4 +96,74 @@ public class PrometheusMetricsExportAutoConfiguration {
}
+ /**
+ * Configuration for Prometheus
+ * Pushgateway.
+ */
+ @Configuration(proxyBeanMethods = false)
+ @ConditionalOnClass(PushGateway.class)
+ @ConditionalOnProperty(prefix = "management.prometheus.metrics.export.pushgateway", name = "enabled")
+ 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
+ PrometheusPushGatewayManager prometheusPushGatewayManager(PrometheusRegistry registry,
+ PrometheusProperties prometheusProperties, Environment environment) {
+ PrometheusProperties.Pushgateway properties = prometheusProperties.getPushgateway();
+ PushGateway pushGateway = initializePushGateway(registry, properties, environment);
+ return new PrometheusPushGatewayManager(pushGateway, properties.getPushRate(),
+ properties.getShutdownOperation());
+ }
+
+ private PushGateway initializePushGateway(PrometheusRegistry registry,
+ PrometheusProperties.Pushgateway properties, Environment environment) {
+ Builder builder = PushGateway.builder()
+ .address(properties.getAddress())
+ .scheme(scheme(properties))
+ .format(format(properties))
+ .job(getJob(properties, environment))
+ .registry(registry);
+ MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> {
+ entries.put("management.prometheus.metrics.export.pushgateway.token", properties.getToken());
+ entries.put("management.prometheus.metrics.export.pushgateway.username", properties.getUsername());
+ });
+ if (StringUtils.hasText(properties.getToken())) {
+ builder.bearerToken(properties.getToken());
+ }
+ else if (StringUtils.hasText(properties.getUsername())) {
+ builder.basicAuth(properties.getUsername(), properties.getPassword());
+ }
+ properties.getGroupingKey().forEach(builder::groupingKey);
+ return builder.build();
+ }
+
+ private Scheme scheme(PrometheusProperties.Pushgateway properties) {
+ return switch (properties.getScheme()) {
+ case HTTP -> Scheme.HTTP;
+ case HTTPS -> Scheme.HTTPS;
+ };
+ }
+
+ private Format format(PrometheusProperties.Pushgateway properties) {
+ return switch (properties.getFormat()) {
+ case PROTOBUF -> Format.PROMETHEUS_PROTOBUF;
+ case TEXT -> Format.PROMETHEUS_TEXT;
+ };
+ }
+
+ 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;
+ }
+
+ }
+
}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusProperties.java
index 1887d2237c5..76448adc8a0 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusProperties.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusProperties.java
@@ -104,9 +104,14 @@ public class PrometheusProperties {
private Boolean enabled = false;
/**
- * Base URL for the Pushgateway.
+ * Address (host:port) for the Pushgateway.
*/
- private String baseUrl = "http://localhost:9091";
+ private String address = "localhost:9091";
+
+ /**
+ * The scheme to use when pushing metrics.
+ */
+ private Scheme scheme = Scheme.HTTP;
/**
* Login user of the Prometheus Pushgateway.
@@ -118,6 +123,16 @@ public class PrometheusProperties {
*/
private String password;
+ /**
+ * The token to use for authentication with the Prometheus Pushgateway.
+ */
+ private String token;
+
+ /**
+ * The format to use when pushing metrics.
+ */
+ private Format format = Format.PROTOBUF;
+
/**
* Frequency with which to push metrics.
*/
@@ -146,12 +161,12 @@ public class PrometheusProperties {
this.enabled = enabled;
}
- public String getBaseUrl() {
- return this.baseUrl;
+ public String getAddress() {
+ return this.address;
}
- public void setBaseUrl(String baseUrl) {
- this.baseUrl = baseUrl;
+ public void setAddress(String address) {
+ this.address = address;
}
public String getUsername() {
@@ -202,6 +217,58 @@ public class PrometheusProperties {
this.shutdownOperation = shutdownOperation;
}
+ public Scheme getScheme() {
+ return this.scheme;
+ }
+
+ public void setScheme(Scheme scheme) {
+ this.scheme = scheme;
+ }
+
+ public String getToken() {
+ return this.token;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public Format getFormat() {
+ return this.format;
+ }
+
+ public void setFormat(Format format) {
+ this.format = format;
+ }
+
+ public enum Format {
+
+ /**
+ * Push metrics in text format.
+ */
+ TEXT,
+
+ /**
+ * Push metrics in protobuf format.
+ */
+ PROTOBUF
+
+ }
+
+ public enum Scheme {
+
+ /**
+ * Use HTTP to push metrics.
+ */
+ HTTP,
+
+ /**
+ * Use HTTPS to push metrics.
+ */
+ HTTPS
+
+ }
+
}
}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
index 7832165303a..1e95b92bbbe 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -2083,6 +2083,14 @@
"type": "java.lang.Boolean",
"description": "Whether auto-configuration of tracing is enabled to export OTLP traces."
},
+ {
+ "name": "management.promethus.metrics.export.pushgateway.base-url",
+ "type": "java.lang.String",
+ "deprecation": {
+ "level": "error",
+ "replacement": "management.prometheus.metrics.export.pushgateway.address"
+ }
+ },
{
"name": "management.server.add-application-context-header",
"type": "java.lang.Boolean",
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/PrometheusScrapeEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/PrometheusScrapeEndpointDocumentationTests.java
index d744c03fc69..5006501cc69 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/PrometheusScrapeEndpointDocumentationTests.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/PrometheusScrapeEndpointDocumentationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2024 the original author or authors.
+ * Copyright 2012-2025 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.
@@ -21,7 +21,7 @@ import java.util.Properties;
import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
import io.micrometer.prometheusmetrics.PrometheusMeterRegistry;
-import io.prometheus.client.exporter.common.TextFormat;
+import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter;
import io.prometheus.metrics.model.registry.PrometheusRegistry;
import org.junit.jupiter.api.Test;
@@ -50,7 +50,7 @@ class PrometheusScrapeEndpointDocumentationTests extends MockMvcEndpointDocument
@Test
void prometheusOpenmetrics() {
- assertThat(this.mvc.get().uri("/actuator/prometheus").accept(TextFormat.CONTENT_TYPE_OPENMETRICS_100))
+ assertThat(this.mvc.get().uri("/actuator/prometheus").accept(OpenMetricsTextFormatWriter.CONTENT_TYPE))
.satisfies((result) -> {
assertThat(result).hasStatusOk()
.headers()
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java
index 1c26b2ec713..a23671b960b 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2024 the original author or authors.
+ * Copyright 2012-2025 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.
@@ -16,22 +16,38 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus;
+import java.net.MalformedURLException;
+import java.net.URI;
+
import io.micrometer.core.instrument.Clock;
import io.micrometer.prometheusmetrics.PrometheusConfig;
import io.micrometer.prometheusmetrics.PrometheusMeterRegistry;
+import io.prometheus.metrics.exporter.pushgateway.DefaultHttpConnectionFactory;
+import io.prometheus.metrics.exporter.pushgateway.HttpConnectionFactory;
+import io.prometheus.metrics.exporter.pushgateway.PushGateway;
+import io.prometheus.metrics.expositionformats.PrometheusTextFormatWriter;
import io.prometheus.metrics.model.registry.PrometheusRegistry;
import io.prometheus.metrics.tracer.common.SpanContext;
+import org.assertj.core.api.InstanceOfAssertFactories;
+import org.assertj.core.api.ThrowingConsumer;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager;
import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusScrapeEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException;
import org.springframework.boot.test.context.FilteredClassLoader;
+import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.boot.test.context.runner.ContextConsumer;
+import org.springframework.boot.test.system.CapturedOutput;
+import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
+import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -158,6 +174,116 @@ class PrometheusMetricsExportAutoConfigurationTests {
.run((context) -> assertThat(context).doesNotHaveBean(PrometheusPushGatewayManager.class));
}
+ @Test
+ @ExtendWith(OutputCaptureExtension.class)
+ void withPushGatewayEnabled(CapturedOutput output) {
+ this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class))
+ .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true")
+ .withUserConfiguration(BaseConfiguration.class)
+ .run((context) -> {
+ assertThat(output).doesNotContain("Invalid PushGateway base url");
+ hasGatewayUrl(context, "http://localhost:9091/metrics/job/spring");
+ });
+ }
+
+ @Test
+ void withPushGatewayNoBasicAuth() {
+ this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class))
+ .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true")
+ .withUserConfiguration(BaseConfiguration.class)
+ .run(hasHttpConnectionFactory((httpConnectionFactory) -> assertThat(httpConnectionFactory)
+ .isInstanceOf(DefaultHttpConnectionFactory.class)));
+ }
+
+ @Test
+ void withCustomPushGatewayAddress() {
+ this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class))
+ .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true",
+ "management.prometheus.metrics.export.pushgateway.address=localhost:8080")
+ .withUserConfiguration(BaseConfiguration.class)
+ .run((context) -> hasGatewayUrl(context, "http://localhost:8080/metrics/job/spring"));
+ }
+
+ @Test
+ void withCustomScheme() {
+ this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class))
+ .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true",
+ "management.prometheus.metrics.export.pushgateway.scheme=https")
+ .withUserConfiguration(BaseConfiguration.class)
+ .run((context) -> hasGatewayUrl(context, "https://localhost:9091/metrics/job/spring"));
+ }
+
+ @Test
+ void withCustomFormat() {
+ this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class))
+ .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true",
+ "management.prometheus.metrics.export.pushgateway.format=text")
+ .withUserConfiguration(BaseConfiguration.class)
+ .run((context) -> assertThat(getPushGateway(context)).extracting("writer")
+ .isInstanceOf(PrometheusTextFormatWriter.class));
+ }
+
+ @Test
+ void withPushGatewayBasicAuth() {
+ this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class))
+ .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true",
+ "management.prometheus.metrics.export.pushgateway.username=admin",
+ "management.prometheus.metrics.export.pushgateway.password=secret")
+ .withUserConfiguration(BaseConfiguration.class)
+ .run((context) -> assertThat(getPushGateway(context))
+ .extracting("requestHeaders", InstanceOfAssertFactories.map(String.class, String.class))
+ .satisfies((headers) -> assertThat(headers.get("Authorization")).startsWith("Basic ")));
+
+ }
+
+ @Test
+ void withPushGatewayBearerToken() {
+ this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class))
+ .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true",
+ "management.prometheus.metrics.export.pushgateway.token=a1b2c3d4")
+ .withUserConfiguration(BaseConfiguration.class)
+ .run((context) -> assertThat(getPushGateway(context))
+ .extracting("requestHeaders", InstanceOfAssertFactories.map(String.class, String.class))
+ .satisfies((headers) -> assertThat(headers.get("Authorization")).startsWith("Bearer ")));
+ }
+
+ @Test
+ void failsFastWithBothBearerAndBasicAuthentication() {
+ this.contextRunner.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class))
+ .withPropertyValues("management.prometheus.metrics.export.pushgateway.enabled=true",
+ "management.prometheus.metrics.export.pushgateway.username=alice",
+ "management.prometheus.metrics.export.pushgateway.token=a1b2c3d4")
+ .withUserConfiguration(BaseConfiguration.class)
+ .run((context) -> assertThat(context).getFailure()
+ .hasRootCauseInstanceOf(MutuallyExclusiveConfigurationPropertiesException.class)
+ .hasMessageContainingAll("management.prometheus.metrics.export.pushgateway.username",
+ "management.prometheus.metrics.export.pushgateway.token"));
+ }
+
+ private void hasGatewayUrl(AssertableApplicationContext context, String url) {
+ try {
+ assertThat(getPushGateway(context)).hasFieldOrPropertyWithValue("url", URI.create(url).toURL());
+ }
+ catch (MalformedURLException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private ContextConsumer hasHttpConnectionFactory(
+ ThrowingConsumer httpConnectionFactory) {
+ return (context) -> {
+ PushGateway pushGateway = getPushGateway(context);
+ httpConnectionFactory
+ .accept((HttpConnectionFactory) ReflectionTestUtils.getField(pushGateway, "connectionFactory"));
+ };
+ }
+
+ private PushGateway getPushGateway(AssertableApplicationContext context) {
+ assertThat(context).hasSingleBean(PrometheusPushGatewayManager.class);
+ PrometheusPushGatewayManager gatewayManager = context.getBean(PrometheusPushGatewayManager.class);
+ return (PushGateway) ReflectionTestUtils.getField(gatewayManager, "pushGateway");
+ }
+
@Configuration(proxyBeanMethods = false)
static class BaseConfiguration {
diff --git a/spring-boot-project/spring-boot-actuator/build.gradle b/spring-boot-project/spring-boot-actuator/build.gradle
index dd23bd47775..90a7233376d 100644
--- a/spring-boot-project/spring-boot-actuator/build.gradle
+++ b/spring-boot-project/spring-boot-actuator/build.gradle
@@ -39,9 +39,7 @@ dependencies {
optional("io.micrometer:micrometer-registry-prometheus")
optional("io.micrometer:micrometer-registry-prometheus-simpleclient")
optional("io.prometheus:prometheus-metrics-exposition-formats")
- optional("io.prometheus:simpleclient_pushgateway") {
- exclude(group: "javax.xml.bind", module: "jaxb-api")
- }
+ optional("io.prometheus:prometheus-metrics-exporter-pushgateway")
optional("io.r2dbc:r2dbc-pool")
optional("io.r2dbc:r2dbc-spi")
optional("io.undertow:undertow-servlet")
diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManager.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManager.java
index da9061c3223..a4c15250a05 100644
--- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManager.java
+++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManager.java
@@ -17,13 +17,11 @@
package org.springframework.boot.actuate.metrics.export.prometheus;
import java.time.Duration;
-import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
-import io.prometheus.client.CollectorRegistry;
-import io.prometheus.client.exporter.PushGateway;
+import io.prometheus.metrics.exporter.pushgateway.PushGateway;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -46,12 +44,6 @@ public class PrometheusPushGatewayManager {
private final PushGateway pushGateway;
- private final CollectorRegistry registry;
-
- private final String job;
-
- private final Map groupingKey;
-
private final ShutdownOperation shutdownOperation;
private final TaskScheduler scheduler;
@@ -59,43 +51,24 @@ public class PrometheusPushGatewayManager {
private final ScheduledFuture> scheduled;
/**
- * Create a new {@link PrometheusPushGatewayManager} instance using a single threaded
- * {@link TaskScheduler}.
+ * Create a new {@link PrometheusPushGatewayManager} instance.
* @param pushGateway the source push gateway
- * @param registry the collector registry to push
* @param pushRate the rate at which push operations occur
- * @param job the job ID for the operation
- * @param groupingKeys an optional set of grouping keys for the operation
* @param shutdownOperation the shutdown operation that should be performed when
* context is closed.
+ * @since 3.5.0
*/
- public PrometheusPushGatewayManager(PushGateway pushGateway, CollectorRegistry registry, Duration pushRate,
- String job, Map groupingKeys, ShutdownOperation shutdownOperation) {
- this(pushGateway, registry, new PushGatewayTaskScheduler(), pushRate, job, groupingKeys, shutdownOperation);
+ public PrometheusPushGatewayManager(PushGateway pushGateway, Duration pushRate,
+ ShutdownOperation shutdownOperation) {
+ this(pushGateway, new PushGatewayTaskScheduler(), pushRate, shutdownOperation);
}
- /**
- * Create a new {@link PrometheusPushGatewayManager} instance.
- * @param pushGateway the source push gateway
- * @param registry the collector registry to push
- * @param scheduler the scheduler used for operations
- * @param pushRate the rate at which push operations occur
- * @param job the job ID for the operation
- * @param groupingKey an optional set of grouping keys for the operation
- * @param shutdownOperation the shutdown operation that should be performed when
- * context is closed.
- */
- public PrometheusPushGatewayManager(PushGateway pushGateway, CollectorRegistry registry, TaskScheduler scheduler,
- Duration pushRate, String job, Map groupingKey, ShutdownOperation shutdownOperation) {
+ PrometheusPushGatewayManager(PushGateway pushGateway, TaskScheduler scheduler, Duration pushRate,
+ ShutdownOperation shutdownOperation) {
Assert.notNull(pushGateway, "'pushGateway' must not be null");
- Assert.notNull(registry, "'registry' must not be null");
Assert.notNull(scheduler, "'scheduler' must not be null");
Assert.notNull(pushRate, "'pushRate' must not be null");
- Assert.hasLength(job, "'job' must not be empty");
this.pushGateway = pushGateway;
- this.registry = registry;
- this.job = job;
- this.groupingKey = groupingKey;
this.shutdownOperation = (shutdownOperation != null) ? shutdownOperation : ShutdownOperation.NONE;
this.scheduler = scheduler;
this.scheduled = this.scheduler.scheduleAtFixedRate(this::post, pushRate);
@@ -103,7 +76,7 @@ public class PrometheusPushGatewayManager {
private void post() {
try {
- this.pushGateway.pushAdd(this.registry, this.job, this.groupingKey);
+ this.pushGateway.pushAdd();
}
catch (Throwable ex) {
logger.warn("Unexpected exception thrown by POST of metrics to Prometheus Pushgateway", ex);
@@ -112,7 +85,7 @@ public class PrometheusPushGatewayManager {
private void put() {
try {
- this.pushGateway.push(this.registry, this.job, this.groupingKey);
+ this.pushGateway.push();
}
catch (Throwable ex) {
logger.warn("Unexpected exception thrown by PUT of metrics to Prometheus Pushgateway", ex);
@@ -121,7 +94,7 @@ public class PrometheusPushGatewayManager {
private void delete() {
try {
- this.pushGateway.delete(this.job, this.groupingKey);
+ this.pushGateway.delete();
}
catch (Throwable ex) {
logger.warn("Unexpected exception thrown by DELETE of metrics from Prometheus Pushgateway", ex);
diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManagerTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManagerTests.java
index d14202c1dd9..cc14ef1c167 100644
--- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManagerTests.java
+++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManagerTests.java
@@ -17,12 +17,9 @@
package org.springframework.boot.actuate.metrics.export.prometheus;
import java.time.Duration;
-import java.util.Collections;
-import java.util.Map;
import java.util.concurrent.ScheduledFuture;
-import io.prometheus.client.CollectorRegistry;
-import io.prometheus.client.exporter.PushGateway;
+import io.prometheus.metrics.exporter.pushgateway.PushGateway;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
@@ -55,16 +52,11 @@ class PrometheusPushGatewayManagerTests {
@Mock
private PushGateway pushGateway;
- @Mock
- private CollectorRegistry registry;
-
@Mock
private TaskScheduler scheduler;
private final Duration pushRate = Duration.ofSeconds(1);
- private final Map groupingKey = Collections.singletonMap("foo", "bar");
-
@Captor
private ArgumentCaptor task;
@@ -74,68 +66,48 @@ class PrometheusPushGatewayManagerTests {
@Test
void createWhenPushGatewayIsNullThrowsException() {
assertThatIllegalArgumentException()
- .isThrownBy(() -> new PrometheusPushGatewayManager(null, this.registry, this.scheduler, this.pushRate,
- "job", this.groupingKey, null))
+ .isThrownBy(() -> new PrometheusPushGatewayManager(null, this.scheduler, this.pushRate, null))
.withMessage("'pushGateway' must not be null");
}
- @Test
- void createWhenCollectorRegistryIsNullThrowsException() {
- assertThatIllegalArgumentException()
- .isThrownBy(() -> new PrometheusPushGatewayManager(this.pushGateway, null, this.scheduler, this.pushRate,
- "job", this.groupingKey, null))
- .withMessage("'registry' must not be null");
- }
-
@Test
void createWhenSchedulerIsNullThrowsException() {
assertThatIllegalArgumentException()
- .isThrownBy(() -> new PrometheusPushGatewayManager(this.pushGateway, this.registry, null, this.pushRate,
- "job", this.groupingKey, null))
+ .isThrownBy(() -> new PrometheusPushGatewayManager(this.pushGateway, null, this.pushRate, null))
.withMessage("'scheduler' must not be null");
}
@Test
void createWhenPushRateIsNullThrowsException() {
assertThatIllegalArgumentException()
- .isThrownBy(() -> new PrometheusPushGatewayManager(this.pushGateway, this.registry, this.scheduler, null,
- "job", this.groupingKey, null))
+ .isThrownBy(() -> new PrometheusPushGatewayManager(this.pushGateway, this.scheduler, null, null))
.withMessage("'pushRate' must not be null");
}
- @Test
- void createWhenJobIsEmptyThrowsException() {
- assertThatIllegalArgumentException()
- .isThrownBy(() -> new PrometheusPushGatewayManager(this.pushGateway, this.registry, this.scheduler,
- this.pushRate, "", this.groupingKey, null))
- .withMessage("'job' must not be empty");
- }
-
@Test
void createShouldSchedulePushAsFixedRate() throws Exception {
- new PrometheusPushGatewayManager(this.pushGateway, this.registry, this.scheduler, this.pushRate, "job",
- this.groupingKey, null);
+ new PrometheusPushGatewayManager(this.pushGateway, this.scheduler, this.pushRate, null);
then(this.scheduler).should().scheduleAtFixedRate(this.task.capture(), eq(this.pushRate));
this.task.getValue().run();
- then(this.pushGateway).should().pushAdd(this.registry, "job", this.groupingKey);
+ then(this.pushGateway).should().pushAdd();
}
@Test
- void shutdownWhenOwnsSchedulerDoesShutdownScheduler() {
+ void shutdownWhenOwnsSchedulerDoesShutDownScheduler() {
PushGatewayTaskScheduler ownedScheduler = givenScheduleAtFixedRateWillReturnFuture(
mock(PushGatewayTaskScheduler.class));
- PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager(this.pushGateway, this.registry,
- ownedScheduler, this.pushRate, "job", this.groupingKey, null);
+ PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager(this.pushGateway, ownedScheduler,
+ this.pushRate, null);
manager.shutdown();
then(ownedScheduler).should().shutdown();
}
@Test
- void shutdownWhenDoesNotOwnSchedulerDoesNotShutdownScheduler() {
+ void shutdownWhenDoesNotOwnSchedulerDoesNotShutDownScheduler() {
ThreadPoolTaskScheduler otherScheduler = givenScheduleAtFixedRateWillReturnFuture(
mock(ThreadPoolTaskScheduler.class));
- PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager(this.pushGateway, this.registry,
- otherScheduler, this.pushRate, "job", this.groupingKey, null);
+ PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager(this.pushGateway, otherScheduler,
+ this.pushRate, null);
manager.shutdown();
then(otherScheduler).should(never()).shutdown();
}
@@ -143,38 +115,38 @@ class PrometheusPushGatewayManagerTests {
@Test
void shutdownWhenShutdownOperationIsPostPerformsPushAddOnShutdown() throws Exception {
givenScheduleAtFixedRateWithReturnFuture();
- PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager(this.pushGateway, this.registry,
- this.scheduler, this.pushRate, "job", this.groupingKey, ShutdownOperation.POST);
+ PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager(this.pushGateway, this.scheduler,
+ this.pushRate, ShutdownOperation.POST);
manager.shutdown();
then(this.future).should().cancel(false);
- then(this.pushGateway).should().pushAdd(this.registry, "job", this.groupingKey);
+ then(this.pushGateway).should().pushAdd();
}
@Test
void shutdownWhenShutdownOperationIsPutPerformsPushOnShutdown() throws Exception {
givenScheduleAtFixedRateWithReturnFuture();
- PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager(this.pushGateway, this.registry,
- this.scheduler, this.pushRate, "job", this.groupingKey, ShutdownOperation.PUT);
+ PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager(this.pushGateway, this.scheduler,
+ this.pushRate, ShutdownOperation.PUT);
manager.shutdown();
then(this.future).should().cancel(false);
- then(this.pushGateway).should().push(this.registry, "job", this.groupingKey);
+ then(this.pushGateway).should().push();
}
@Test
void shutdownWhenShutdownOperationIsDeletePerformsDeleteOnShutdown() throws Exception {
givenScheduleAtFixedRateWithReturnFuture();
- PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager(this.pushGateway, this.registry,
- this.scheduler, this.pushRate, "job", this.groupingKey, ShutdownOperation.DELETE);
+ PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager(this.pushGateway, this.scheduler,
+ this.pushRate, ShutdownOperation.DELETE);
manager.shutdown();
then(this.future).should().cancel(false);
- then(this.pushGateway).should().delete("job", this.groupingKey);
+ then(this.pushGateway).should().delete();
}
@Test
void shutdownWhenShutdownOperationIsNoneDoesNothing() {
givenScheduleAtFixedRateWithReturnFuture();
- PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager(this.pushGateway, this.registry,
- this.scheduler, this.pushRate, "job", this.groupingKey, ShutdownOperation.NONE);
+ PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager(this.pushGateway, this.scheduler,
+ this.pushRate, ShutdownOperation.NONE);
manager.shutdown();
then(this.future).should().cancel(false);
then(this.pushGateway).shouldHaveNoInteractions();
@@ -182,10 +154,9 @@ class PrometheusPushGatewayManagerTests {
@Test
void pushDoesNotThrowException() throws Exception {
- new PrometheusPushGatewayManager(this.pushGateway, this.registry, this.scheduler, this.pushRate, "job",
- this.groupingKey, null);
+ new PrometheusPushGatewayManager(this.pushGateway, this.scheduler, this.pushRate, null);
then(this.scheduler).should().scheduleAtFixedRate(this.task.capture(), eq(this.pushRate));
- willThrow(RuntimeException.class).given(this.pushGateway).pushAdd(this.registry, "job", this.groupingKey);
+ willThrow(RuntimeException.class).given(this.pushGateway).pushAdd();
this.task.getValue().run();
}
diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc
index 155d3d21735..1314b3f6023 100644
--- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc
+++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/metrics.adoc
@@ -551,16 +551,13 @@ Please check the https://prometheus.io/docs/prometheus/latest/feature_flags/#exe
For ephemeral or batch jobs that may not exist long enough to be scraped, you can use https://github.com/prometheus/pushgateway[Prometheus Pushgateway] support to expose the metrics to Prometheus.
-NOTE: The Prometheus Pushgateway only works with the deprecated Prometheus simpleclient for now, until the Prometheus 1.x client adds support for it.
-To switch to the simpleclient, remove `io.micrometer:micrometer-registry-prometheus` from your project and add `io.micrometer:micrometer-registry-prometheus-simpleclient` instead.
-
To enable Prometheus Pushgateway support, add the following dependency to your project:
[source,xml]
----
io.prometheus
- simpleclient_pushgateway
+ io.prometheus:prometheus-metrics-exporter-pushgateway
----