diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml index bd2f32275f8e..132385c7b59d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml @@ -132,6 +132,11 @@ micrometer-registry-prometheus true + + io.prometheus + simpleclient_pushgateway + true + io.micrometer micrometer-registry-signalfx 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 d21bf7e46c0c..910e3d67b3b5 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 @@ -16,10 +16,20 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus; +import java.net.UnknownHostException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import javax.annotation.PreDestroy; + 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.exporter.PushGateway; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; @@ -36,6 +46,7 @@ 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; /** * {@link EnableAutoConfiguration Auto-configuration} for exporting metrics to Prometheus. @@ -86,4 +97,113 @@ public PrometheusScrapeEndpoint prometheusEndpoint( } + /** + * Configuration for Prometheus + * Pushgateway. + * + * @author David J. M. Karlsen + */ + @Configuration + @ConditionalOnClass(PushGateway.class) + @ConditionalOnProperty(prefix = "management.metrics.export.prometheus.pushgateway", name = "enabled") + public static class PrometheusPushGatewayConfiguration { + + @Bean + public PushGatewayHandler pushGatewayHandler(CollectorRegistry collectorRegistry, + PrometheusProperties prometheusProperties, Environment environment) { + return new PushGatewayHandler(collectorRegistry, prometheusProperties, + environment); + } + + static class PushGatewayHandler { + + private final Logger logger = LoggerFactory + .getLogger(PrometheusPushGatewayConfiguration.class); + + private final CollectorRegistry collectorRegistry; + + private final PrometheusProperties.PushgatewayProperties pushgatewayProperties; + + private final PushGateway pushGateway; + + private final Environment environment; + + private final ScheduledExecutorService executorService; + + PushGatewayHandler(CollectorRegistry collectorRegistry, + PrometheusProperties prometheusProperties, Environment environment) { + this.collectorRegistry = collectorRegistry; + this.pushgatewayProperties = prometheusProperties.getPushgateway(); + this.pushGateway = new PushGateway( + this.pushgatewayProperties.getBaseUrl()); + this.environment = environment; + this.executorService = Executors.newSingleThreadScheduledExecutor((r) -> { + Thread thread = new Thread(r); + thread.setDaemon(true); + thread.setName("micrometer-pushgateway"); + return thread; + }); + this.executorService.scheduleAtFixedRate(this::push, 0, + this.pushgatewayProperties.getPushRate().toMillis(), + TimeUnit.MILLISECONDS); + } + + void push() { + try { + this.pushGateway.pushAdd(this.collectorRegistry, getJobName(), + this.pushgatewayProperties.getGroupingKeys()); + } + catch (UnknownHostException ex) { + this.logger.error("Unable to locate host '" + + this.pushgatewayProperties.getBaseUrl() + + "'. No longer attempting metrics publication to this host"); + this.executorService.shutdown(); + } + catch (Throwable throwable) { + this.logger.error("Unable to push metrics to Prometheus Pushgateway", + throwable); + } + } + + @PreDestroy + void shutdown() { + this.executorService.shutdown(); + if (this.pushgatewayProperties.isPushOnShutdown()) { + push(); + } + if (this.pushgatewayProperties.isDeleteOnShutdown()) { + delete(); + } + } + + private void delete() { + try { + this.pushGateway.delete(getJobName(), + this.pushgatewayProperties.getGroupingKeys()); + } + catch (Throwable throwable) { + this.logger.error( + "Unable to delete metrics from Prometheus Pushgateway", + throwable); + } + } + + private String getJobName() { + String job = this.pushgatewayProperties.getJob(); + if (job == null) { + job = this.environment.getProperty("spring.application.name"); + } + if (job == null) { + // There's a history of Prometheus spring integration defaulting the + // getJobName name to "spring" from when + // Prometheus integration didn't exist in Spring itself. + job = "spring"; + } + return 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 3612eee7300b..a1650e1937d0 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 @@ -17,6 +17,8 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus; import java.time.Duration; +import java.util.HashMap; +import java.util.Map; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -36,6 +38,12 @@ public class PrometheusProperties { */ private boolean descriptions = true; + /** + * Configuration options for using Prometheus Pushgateway, allowing metrics to be + * pushed when they cannot be scraped. + */ + private PushgatewayProperties pushgateway = new PushgatewayProperties(); + /** * Step size (i.e. reporting frequency) to use. */ @@ -57,4 +65,110 @@ public void setStep(Duration step) { this.step = step; } + public PushgatewayProperties getPushgateway() { + return this.pushgateway; + } + + public void setPushgateway(PushgatewayProperties pushgateway) { + this.pushgateway = pushgateway; + } + + /** + * Configuration options for push-based interaction with Prometheus. + */ + public static class PushgatewayProperties { + + /** + * Enable publishing via a Prometheus Pushgateway. + */ + private Boolean enabled = false; + + /** + * Required host:port or ip:port of the Pushgateway. + */ + private String baseUrl = "localhost:9091"; + + /** + * Required identifier for this application instance. + */ + private String job; + + /** + * Frequency with which to push metrics to Pushgateway. + */ + private Duration pushRate = Duration.ofMinutes(1); + + /** + * Push metrics right before shut-down. Mostly useful for batch jobs. + */ + private boolean pushOnShutdown = true; + + /** + * Delete metrics from Pushgateway when application is shut-down. + */ + private boolean deleteOnShutdown = true; + + /** + * Used to group metrics in pushgateway. A common example is setting + */ + private Map groupingKeys = new HashMap<>(); + + public Boolean getEnabled() { + return this.enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public String getBaseUrl() { + return this.baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + public String getJob() { + return this.job; + } + + public void setJob(String job) { + this.job = job; + } + + public Duration getPushRate() { + return this.pushRate; + } + + public void setPushRate(Duration pushRate) { + this.pushRate = pushRate; + } + + public boolean isPushOnShutdown() { + return this.pushOnShutdown; + } + + public void setPushOnShutdown(boolean pushOnShutdown) { + this.pushOnShutdown = pushOnShutdown; + } + + public boolean isDeleteOnShutdown() { + return this.deleteOnShutdown; + } + + public void setDeleteOnShutdown(boolean deleteOnShutdown) { + this.deleteOnShutdown = deleteOnShutdown; + } + + public Map getGroupingKeys() { + return this.groupingKeys; + } + + public void setGroupingKeys(Map groupingKeys) { + this.groupingKeys = groupingKeys; + } + + } + } 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 6d89f6c315d8..56155d6739b7 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 @@ -22,6 +22,7 @@ import io.prometheus.client.CollectorRegistry; import org.junit.Test; +import org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusMetricsExportAutoConfiguration.PrometheusPushGatewayConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusScrapeEndpoint; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -128,6 +129,18 @@ public void allowsCustomScrapeEndpointToBeUsed() { .hasSingleBean(PrometheusScrapeEndpoint.class)); } + @Test + public void withPushGatewayEnabled() { + this.contextRunner + .withConfiguration( + AutoConfigurations.of(ManagementContextAutoConfiguration.class)) + .withPropertyValues( + "management.metrics.export.prometheus.pushgateway.enabled=true") + .withUserConfiguration(BaseConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean( + PrometheusPushGatewayConfiguration.PushGatewayHandler.class)); + } + @Configuration static class BaseConfiguration { diff --git a/spring-boot-project/spring-boot-dependencies/pom.xml b/spring-boot-project/spring-boot-dependencies/pom.xml index b70292d8f833..e7e163b9c791 100644 --- a/spring-boot-project/spring-boot-dependencies/pom.xml +++ b/spring-boot-project/spring-boot-dependencies/pom.xml @@ -140,6 +140,7 @@ 1.1.0 1.0.3 42.2.5 + 0.5.0 2.3.0 4.2.1 5.4.1 @@ -941,6 +942,11 @@ netty-tcnative-boringssl-static ${netty-tcnative.version} + + io.prometheus + simpleclient_pushgateway + ${prometheus-pushgateway.version} + io.projectreactor reactor-bom