diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml index 15bbe47126d..4ec774fe71e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml @@ -147,6 +147,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 d21bf7e46c0..a011ddf7b96 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,15 +16,21 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus; +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.exporter.PushGateway; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; 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.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; @@ -36,12 +42,14 @@ 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; /** * {@link EnableAutoConfiguration Auto-configuration} for exporting metrics to Prometheus. * * @since 2.0.0 * @author Jon Schneider + * @author David J. M. Karlsen */ @Configuration @AutoConfigureBefore({ CompositeMeterRegistryAutoConfiguration.class, @@ -86,4 +94,47 @@ public class PrometheusMetricsExportAutoConfiguration { } + /** + * Configuration for Prometheus + * Pushgateway. + */ + @Configuration + @ConditionalOnClass(PushGateway.class) + @ConditionalOnProperty(prefix = "management.metrics.export.prometheus.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) { + PrometheusProperties.Pushgateway properties = prometheusProperties + .getPushgateway(); + PushGateway pushGateway = new PushGateway(properties.getBaseUrl()); + Duration pushRate = properties.getPushRate(); + String job = getJob(properties, environment); + Map groupingKey = properties.getGroupingKey(); + ShutdownOperation shutdownOperation = properties.getShutdownOperation(); + return new PrometheusPushGatewayManager(pushGateway, collectorRegistry, + pushRate, job, groupingKey, shutdownOperation); + } + + private String getJob(PrometheusProperties.Pushgateway properties, + Environment environment) { + String job = properties.getJob(); + job = (job != null) ? job + : environment.getProperty("spring.application.name"); + job = (job != null) ? job : FALLBACK_JOB; + 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 3612eee7300..0fc7a9ccb1a 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,7 +17,10 @@ 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.actuate.metrics.export.prometheus.PrometheusPushGatewayManager.ShutdownOperation; import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -36,6 +39,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 Pushgateway pushgateway = new Pushgateway(); + /** * Step size (i.e. reporting frequency) to use. */ @@ -57,4 +66,97 @@ public class PrometheusProperties { this.step = step; } + public Pushgateway getPushgateway() { + return this.pushgateway; + } + + public void setPushgateway(Pushgateway pushgateway) { + this.pushgateway = pushgateway; + } + + /** + * Configuration options for push-based interaction with Prometheus. + */ + public static class Pushgateway { + + /** + * Enable publishing via a Prometheus Pushgateway. + */ + private Boolean enabled = false; + + /** + * Base URL for the Pushgateway. + */ + private String baseUrl = "localhost:9091"; + + /** + * Frequency with which to push metrics. + */ + private Duration pushRate = Duration.ofMinutes(1); + + /** + * Job identifier for this application instance. + */ + private String job; + + /** + * Grouping key for the pushed metrics. + */ + private Map groupingKey = new HashMap<>(); + + /** + * Operation that should be performed on shutdown. + */ + private ShutdownOperation shutdownOperation = ShutdownOperation.NONE; + + 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 Duration getPushRate() { + return this.pushRate; + } + + public void setPushRate(Duration pushRate) { + this.pushRate = pushRate; + } + + public String getJob() { + return this.job; + } + + public void setJob(String job) { + this.job = job; + } + + public Map getGroupingKey() { + return this.groupingKey; + } + + public void setGroupingKey(Map groupingKey) { + this.groupingKey = groupingKey; + } + + public ShutdownOperation getShutdownOperation() { + return this.shutdownOperation; + } + + public void setShutdownOperation(ShutdownOperation shutdownOperation) { + this.shutdownOperation = shutdownOperation; + } + + } + } 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 6d89f6c315d..52f80208d80 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 @@ -23,6 +23,7 @@ import io.prometheus.client.CollectorRegistry; import org.junit.Test; 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.test.context.runner.ApplicationContextRunner; @@ -128,6 +129,18 @@ public class PrometheusMetricsExportAutoConfigurationTests { .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(PrometheusPushGatewayManager.class)); + } + @Configuration static class BaseConfiguration { diff --git a/spring-boot-project/spring-boot-actuator/pom.xml b/spring-boot-project/spring-boot-actuator/pom.xml index ba0b2dd3419..a02ba2d1e77 100644 --- a/spring-boot-project/spring-boot-actuator/pom.xml +++ b/spring-boot-project/spring-boot-actuator/pom.xml @@ -1,5 +1,6 @@ - 4.0.0 @@ -61,6 +62,11 @@ micrometer-registry-prometheus true + + io.prometheus + simpleclient_pushgateway + true + io.reactivex rxjava-reactive-streams 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 new file mode 100644 index 00000000000..9e9e50b3024 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManager.java @@ -0,0 +1,201 @@ +/* + * Copyright 2012-2018 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 + * + * http://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.metrics.export.prometheus; + +import java.net.UnknownHostException; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Class that can be used to managed the pushing of metrics to a {@link PushGateway + * Prometheus PushGateway}. Handles the scheduling of push operations, error handling and + * shutdown operations. + * + * @author David J. M. Karlsen + * @author Phillip Webb + * @since 2.1.0 + */ +public class PrometheusPushGatewayManager { + + private static final Logger logger = LoggerFactory + .getLogger(PrometheusPushGatewayManager.class); + + private final PushGateway pushGateway; + + private final CollectorRegistry registry; + + private final String job; + + private final Map groupingKey; + + private final ShutdownOperation shutdownOperation; + + private final TaskScheduler scheduler; + + private ScheduledFuture scheduled; + + /** + * Create a new {@link PrometheusPushGatewayManager} instance using a single threaded + * {@link TaskScheduler}. + * @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. + */ + public PrometheusPushGatewayManager(PushGateway pushGateway, + CollectorRegistry registry, Duration pushRate, String job, + Map groupingKeys, ShutdownOperation shutdownOperation) { + this(pushGateway, registry, new PushGatewayTaskScheduler(), pushRate, job, + groupingKeys, 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) { + 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::push, pushRate); + } + + private void push() { + try { + this.pushGateway.pushAdd(this.registry, this.job, this.groupingKey); + } + catch (UnknownHostException ex) { + String host = ex.getMessage(); + String message = "Unable to locate prometheus push gateway host"; + message += StringUtils.hasLength(host) ? " '" + host + "'" : ""; + message += ". No longer attempting metrics publication to this host"; + logger.error(message, ex); + shutdown(ShutdownOperation.NONE); + } + catch (Throwable ex) { + logger.error("Unable to push metrics to Prometheus Pushgateway", ex); + } + } + + private void delete() { + try { + this.pushGateway.delete(this.job, this.groupingKey); + } + catch (Throwable ex) { + logger.error("Unable to delete metrics from Prometheus Pushgateway", ex); + } + } + + /** + * Shutdown the manager, running any {@link ShutdownOperation}. + */ + public void shutdown() { + shutdown(this.shutdownOperation); + } + + private void shutdown(ShutdownOperation shutdownOperation) { + if (this.scheduler instanceof PushGatewayTaskScheduler) { + ((PushGatewayTaskScheduler) this.scheduler).shutdown(); + } + this.scheduled.cancel(false); + switch (shutdownOperation) { + case PUSH: + push(); + break; + case DELETE: + delete(); + break; + } + } + + /** + * The operation that should be performed on shutdown. + */ + public enum ShutdownOperation { + + /** + * Don't perform any shutdown operation. + */ + NONE, + + /** + * Perform a 'push' before shutdown. + */ + PUSH, + + /** + * Perform a 'delete' before shutdown. + */ + DELETE + + } + + /** + * {@link TaskScheduler} used when the user doesn't specify one. + */ + static class PushGatewayTaskScheduler extends ThreadPoolTaskScheduler { + + PushGatewayTaskScheduler() { + setPoolSize(1); + setDaemon(true); + setThreadGroupName("prometheus-push-gateway"); + } + + @Override + public ScheduledExecutorService getScheduledExecutor() + throws IllegalStateException { + return Executors.newSingleThreadScheduledExecutor(this::newThread); + } + + } + +} 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 new file mode 100644 index 00000000000..acb382213a0 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManagerTests.java @@ -0,0 +1,215 @@ +/* + * Copyright 2012-2018 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 + * + * http://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.metrics.export.prometheus; + +import java.net.UnknownHostException; +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 org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager.PushGatewayTaskScheduler; +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager.ShutdownOperation; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Tests for {@link PrometheusPushGatewayManager}. + * + * @author Phillip Webb + */ +public class PrometheusPushGatewayManagerTests { + + @Mock + private PushGateway pushGateway; + + @Mock + private CollectorRegistry registry; + + private TaskScheduler scheduler; + + private Duration pushRate = Duration.ofSeconds(1); + + private Map groupingKey = Collections.singletonMap("foo", "bar"); + + @Captor + private ArgumentCaptor task; + + @Mock + private ScheduledFuture future; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + this.scheduler = mockScheduler(TaskScheduler.class); + } + + @Test + public void createWhenPushGatewayIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new PrometheusPushGatewayManager(null, this.registry, + this.scheduler, this.pushRate, "job", this.groupingKey, null)) + .withMessage("PushGateway must not be null"); + } + + @Test + public void createWhenCollectorRegistryIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new PrometheusPushGatewayManager(this.pushGateway, null, + this.scheduler, this.pushRate, "job", this.groupingKey, null)) + .withMessage("Registry must not be null"); + } + + @Test + public void createWhenSchedulerIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy( + () -> new PrometheusPushGatewayManager(this.pushGateway, this.registry, + null, this.pushRate, "job", this.groupingKey, null)) + .withMessage("Scheduler must not be null"); + } + + @Test + public void createWhenPushRateIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy( + () -> new PrometheusPushGatewayManager(this.pushGateway, this.registry, + this.scheduler, null, "job", this.groupingKey, null)) + .withMessage("PushRate must not be null"); + } + + @Test + public void createWhenJobIsEmptyThrowsException() { + assertThatIllegalArgumentException().isThrownBy( + () -> new PrometheusPushGatewayManager(this.pushGateway, this.registry, + this.scheduler, this.pushRate, "", this.groupingKey, null)) + .withMessage("Job must not be empty"); + } + + @Test + public void createShouldSchedulePushAsFixedRate() throws Exception { + new PrometheusPushGatewayManager(this.pushGateway, this.registry, this.scheduler, + this.pushRate, "job", this.groupingKey, null); + verify(this.scheduler).scheduleAtFixedRate(this.task.capture(), + eq(this.pushRate)); + this.task.getValue().run(); + verify(this.pushGateway).pushAdd(this.registry, "job", this.groupingKey); + } + + @Test + public void shutdownWhenOwnsSchedulerDoesShutdownScheduler() { + PushGatewayTaskScheduler ownedScheduler = mockScheduler( + PushGatewayTaskScheduler.class); + PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager( + this.pushGateway, this.registry, ownedScheduler, this.pushRate, "job", + this.groupingKey, null); + manager.shutdown(); + verify(ownedScheduler).shutdown(); + } + + @Test + public void shutdownWhenDoesNotOwnSchedulerDoesNotShutdownScheduler() { + ThreadPoolTaskScheduler otherScheduler = mockScheduler( + ThreadPoolTaskScheduler.class); + PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager( + this.pushGateway, this.registry, otherScheduler, this.pushRate, "job", + this.groupingKey, null); + manager.shutdown(); + verify(otherScheduler, never()).shutdown(); + } + + @Test + public void shutdownWhenShutdownOperationIsPushPerformsPushOnShutdown() + throws Exception { + PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager( + this.pushGateway, this.registry, this.scheduler, this.pushRate, "job", + this.groupingKey, ShutdownOperation.PUSH); + manager.shutdown(); + verify(this.future).cancel(false); + verify(this.pushGateway).pushAdd(this.registry, "job", this.groupingKey); + } + + @Test + public void shutdownWhenShutdownOperationIsDeletePerformsDeleteOnShutdown() + throws Exception { + PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager( + this.pushGateway, this.registry, this.scheduler, this.pushRate, "job", + this.groupingKey, ShutdownOperation.DELETE); + manager.shutdown(); + verify(this.future).cancel(false); + verify(this.pushGateway).delete("job", this.groupingKey); + } + + @Test + public void shutdownWhenShutdownOperationIsNoneDoesNothing() { + PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager( + this.pushGateway, this.registry, this.scheduler, this.pushRate, "job", + this.groupingKey, ShutdownOperation.NONE); + manager.shutdown(); + verify(this.future).cancel(false); + verifyZeroInteractions(this.pushGateway); + } + + @Test + public void pushWhenUnknownHostExceptionIsThrownDoesShutdown() throws Exception { + new PrometheusPushGatewayManager(this.pushGateway, this.registry, this.scheduler, + this.pushRate, "job", this.groupingKey, null); + verify(this.scheduler).scheduleAtFixedRate(this.task.capture(), + eq(this.pushRate)); + willThrow(new UnknownHostException("foo")).given(this.pushGateway) + .pushAdd(this.registry, "job", this.groupingKey); + this.task.getValue().run(); + verify(this.future).cancel(false); + } + + @Test + public void pushDoesNotThrowException() throws Exception { + new PrometheusPushGatewayManager(this.pushGateway, this.registry, this.scheduler, + this.pushRate, "job", this.groupingKey, null); + verify(this.scheduler).scheduleAtFixedRate(this.task.capture(), + eq(this.pushRate)); + willThrow(RuntimeException.class).given(this.pushGateway).pushAdd(this.registry, + "job", this.groupingKey); + this.task.getValue().run(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private T mockScheduler(Class type) { + T scheduler = mock(type); + given(scheduler.scheduleAtFixedRate(isA(Runnable.class), isA(Duration.class))) + .willReturn((ScheduledFuture) this.future); + return scheduler; + } + +} diff --git a/spring-boot-project/spring-boot-dependencies/pom.xml b/spring-boot-project/spring-boot-dependencies/pom.xml index da05b0973ee..c3f89051a79 100644 --- a/spring-boot-project/spring-boot-dependencies/pom.xml +++ b/spring-boot-project/spring-boot-dependencies/pom.xml @@ -143,6 +143,7 @@ 1.1.0 1.0.3 42.2.5 + 0.5.0 2.3.0 4.2.1 5.4.1 @@ -989,6 +990,11 @@ netty-tcnative-boringssl-static ${netty-tcnative.version} + + io.prometheus + simpleclient_pushgateway + ${prometheus-pushgateway.version} + io.projectreactor reactor-bom diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index e638bc3967f..ea5b8de2daf 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -1480,6 +1480,12 @@ content into your application. Rather, pick only the properties that you need. management.metrics.export.prometheus.descriptions=true # Whether to enable publishing descriptions as part of the scrape payload to Prometheus. Turn this off to minimize the amount of data sent on each scrape. management.metrics.export.prometheus.enabled=true # Whether exporting of metrics to Prometheus is enabled. management.metrics.export.prometheus.step=1m # Step size (i.e. reporting frequency) to use. + management.metrics.export.prometheus.pushgateway.base-url=localhost:9091 # Base URL for the Pushgateway. + management.metrics.export.prometheus.pushgateway.enabled=false # Enable publishing via a Prometheus Pushgateway. + management.metrics.export.prometheus.pushgateway.grouping-key= # Grouping key for the pushed metrics. + management.metrics.export.prometheus.pushgateway.job= # Job identifier for this application instance. + management.metrics.export.prometheus.pushgateway.push-rate=1m # Frequency with which to push metrics. + management.metrics.export.prometheus.pushgateway.shutdown-operation= # Operation that should be performed on shutdown (none, push, delete). management.metrics.export.signalfx.access-token= # SignalFX access token. management.metrics.export.signalfx.batch-size=10000 # Number of measurements per request to use for this backend. If more measurements are found, then multiple requests will be made. management.metrics.export.signalfx.connect-timeout=1s # Connection timeout for requests to this backend.