diff --git a/pom.xml b/pom.xml index 7948a76..d2bc631 100644 --- a/pom.xml +++ b/pom.xml @@ -14,12 +14,13 @@ org.springframework.boot spring-boot-starter-parent - 1.4.0.RELEASE + 1.4.4.RELEASE UTF-8 1.8 + 0.0.21 @@ -32,23 +33,24 @@ spring-boot-starter-data-mongodb - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.cloud + org.springframework.cloud spring-cloud-starter-zipkin 1.1.0.RELEASE - + + + io.prometheus + simpleclient_spring_boot + ${prometheus.version} + io.prometheus - simpleclient - RELEASE + simpleclient_hotspot + ${prometheus.version} io.prometheus - simpleclient_common - RELEASE + simpleclient_servlet + ${prometheus.version} org.springframework.data diff --git a/src/main/java/works/weave/socks/orders/config/PrometheusAutoConfiguration.java b/src/main/java/works/weave/socks/orders/config/PrometheusAutoConfiguration.java new file mode 100644 index 0000000..bdbdc37 --- /dev/null +++ b/src/main/java/works/weave/socks/orders/config/PrometheusAutoConfiguration.java @@ -0,0 +1,35 @@ +package works.weave.socks.orders.config; + +import io.prometheus.client.exporter.MetricsServlet; +import io.prometheus.client.hotspot.DefaultExports; +import io.prometheus.client.spring.boot.SpringBootMetricsCollector; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.endpoint.PublicMetrics; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Collection; + +@Configuration +@ConditionalOnClass(SpringBootMetricsCollector.class) +class PrometheusAutoConfiguration { + @Bean + @ConditionalOnMissingBean(SpringBootMetricsCollector.class) + SpringBootMetricsCollector springBootMetricsCollector(Collection publicMetrics) { + SpringBootMetricsCollector springBootMetricsCollector = new SpringBootMetricsCollector + (publicMetrics); + springBootMetricsCollector.register(); + return springBootMetricsCollector; + } + + @Bean + @ConditionalOnMissingBean(name = "prometheusMetricsServletRegistrationBean") + ServletRegistrationBean prometheusMetricsServletRegistrationBean(@Value("${prometheus.metrics" + + ".path:/metrics}") String metricsPath) { + DefaultExports.initialize(); + return new ServletRegistrationBean(new MetricsServlet(), metricsPath); + } +} diff --git a/src/main/java/works/weave/socks/orders/config/PrometheusEndpointContextConfiguration.java b/src/main/java/works/weave/socks/orders/config/PrometheusEndpointContextConfiguration.java deleted file mode 100644 index 1ab574e..0000000 --- a/src/main/java/works/weave/socks/orders/config/PrometheusEndpointContextConfiguration.java +++ /dev/null @@ -1,40 +0,0 @@ -package works.weave.socks.orders.config; - -import io.prometheus.client.CollectorRegistry; -import org.springframework.boot.actuate.autoconfigure.ExportMetricWriter; -import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration; -import org.springframework.boot.actuate.condition.ConditionalOnEnabledEndpoint; -import org.springframework.boot.actuate.metrics.writer.MetricWriter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.context.annotation.Bean; -import works.weave.socks.orders.controllers.PrometheusEndpoint; -import works.weave.socks.orders.controllers.PrometheusMvcEndpoint; -import works.weave.socks.orders.monitoring.PrometheusMetricWriter; - -@ManagementContextConfiguration -public class PrometheusEndpointContextConfiguration { - - @Bean - public PrometheusEndpoint prometheusEndpoint(CollectorRegistry registry) { - return new PrometheusEndpoint(registry); - } - - @Bean - @ConditionalOnBean(PrometheusEndpoint.class) - @ConditionalOnEnabledEndpoint("prometheus") - PrometheusMvcEndpoint prometheusMvcEndpoint(PrometheusEndpoint prometheusEndpoint) { - return new PrometheusMvcEndpoint(prometheusEndpoint); - } - - @Bean - CollectorRegistry collectorRegistry() { - return new CollectorRegistry(); - } - - @Bean - @ExportMetricWriter - MetricWriter prometheusMetricWriter(CollectorRegistry registry) { - return new PrometheusMetricWriter(registry); - } - -} diff --git a/src/main/java/works/weave/socks/orders/config/WebMvcConfig.java b/src/main/java/works/weave/socks/orders/config/WebMvcConfig.java new file mode 100644 index 0000000..c458516 --- /dev/null +++ b/src/main/java/works/weave/socks/orders/config/WebMvcConfig.java @@ -0,0 +1,25 @@ +package works.weave.socks.orders.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import works.weave.socks.orders.middleware.HTTPMonitoringInterceptor; + +@Configuration +public class WebMvcConfig extends WebMvcConfigurerAdapter { + @Autowired + private HTTPMonitoringInterceptor httpMonitoringInterceptor; + + @Bean + HTTPMonitoringInterceptor httpMonitoringInterceptor() { + return new HTTPMonitoringInterceptor(); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(httpMonitoringInterceptor) + .addPathPatterns("/**"); + } +} diff --git a/src/main/java/works/weave/socks/orders/controllers/PrometheusEndpoint.java b/src/main/java/works/weave/socks/orders/controllers/PrometheusEndpoint.java deleted file mode 100644 index 718153d..0000000 --- a/src/main/java/works/weave/socks/orders/controllers/PrometheusEndpoint.java +++ /dev/null @@ -1,30 +0,0 @@ -package works.weave.socks.orders.controllers; - -import io.prometheus.client.CollectorRegistry; -import io.prometheus.client.exporter.common.TextFormat; -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; - -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; - -public class PrometheusEndpoint extends AbstractEndpoint { - - private CollectorRegistry registry; - - public PrometheusEndpoint(CollectorRegistry registry) { - super("prometheus", false, true); - this.registry = registry; - } - - @Override - public String invoke() { - Writer writer = new StringWriter(); - try { - TextFormat.write004(writer, registry.metricFamilySamples()); - } catch (IOException e) { - e.printStackTrace(); - } - return writer.toString(); - } -} diff --git a/src/main/java/works/weave/socks/orders/controllers/PrometheusMvcEndpoint.java b/src/main/java/works/weave/socks/orders/controllers/PrometheusMvcEndpoint.java deleted file mode 100644 index 303f2c7..0000000 --- a/src/main/java/works/weave/socks/orders/controllers/PrometheusMvcEndpoint.java +++ /dev/null @@ -1,31 +0,0 @@ -package works.weave.socks.orders.controllers; - -import io.prometheus.client.exporter.common.TextFormat; -import org.springframework.boot.actuate.endpoint.mvc.AbstractEndpointMvcAdapter; -import org.springframework.boot.actuate.endpoint.mvc.HypermediaDisabled; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; - -import java.util.Collections; - -public class PrometheusMvcEndpoint extends AbstractEndpointMvcAdapter { - - public PrometheusMvcEndpoint(PrometheusEndpoint delegate) { - super(delegate); - } - - @RequestMapping(method = RequestMethod.GET, produces = TextFormat.CONTENT_TYPE_004) - @ResponseBody - @HypermediaDisabled - protected Object invoke() { - if (!getDelegate().isEnabled()) { - return new ResponseEntity<>( - Collections.singletonMap("message", "This endpoint is disabled"), - HttpStatus.NOT_FOUND); - } - return super.invoke(); - } -} diff --git a/src/main/java/works/weave/socks/orders/middleware/HTTPMonitoringInterceptor.java b/src/main/java/works/weave/socks/orders/middleware/HTTPMonitoringInterceptor.java new file mode 100644 index 0000000..e8c5298 --- /dev/null +++ b/src/main/java/works/weave/socks/orders/middleware/HTTPMonitoringInterceptor.java @@ -0,0 +1,48 @@ +package works.weave.socks.orders.middleware; + +import io.prometheus.client.Histogram; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class HTTPMonitoringInterceptor implements HandlerInterceptor { + static final Histogram requestLatency = Histogram.build() + .name("request_duration_seconds") + .help("Request duration in seconds.") + .labelNames("service", "method", "route", "status_code") + .register(); + + private static final String startTimeKey = "startTime"; + + @Value("${spring.application.name:orders}") + private String serviceName; + + @Override + public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse + httpServletResponse, Object o) throws Exception { + httpServletRequest.setAttribute(startTimeKey, System.nanoTime()); + return true; + } + + @Override + public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse + httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { + long start = (long) httpServletRequest.getAttribute(startTimeKey); + long elapsed = System.nanoTime() - start; + double seconds = (double) elapsed / 1000000000.0; + requestLatency.labels( + serviceName, + httpServletRequest.getMethod(), + httpServletRequest.getServletPath(), + Integer.toString(httpServletResponse.getStatus()) + ).observe(seconds); + } + + @Override + public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse + httpServletResponse, Object o, Exception e) throws Exception { + } +} diff --git a/src/main/java/works/weave/socks/orders/monitoring/PrometheusMetricWriter.java b/src/main/java/works/weave/socks/orders/monitoring/PrometheusMetricWriter.java deleted file mode 100644 index 46182f7..0000000 --- a/src/main/java/works/weave/socks/orders/monitoring/PrometheusMetricWriter.java +++ /dev/null @@ -1,54 +0,0 @@ -package works.weave.socks.orders.monitoring; - -import io.prometheus.client.CollectorRegistry; -import io.prometheus.client.Counter; -import io.prometheus.client.Gauge; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.metrics.Metric; -import org.springframework.boot.actuate.metrics.writer.Delta; -import org.springframework.boot.actuate.metrics.writer.MetricWriter; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -public class PrometheusMetricWriter implements MetricWriter { - - private final ConcurrentMap counters = new ConcurrentHashMap<>(); - private final ConcurrentMap gauges = new ConcurrentHashMap<>(); - private CollectorRegistry registry; - - @Autowired - public PrometheusMetricWriter(CollectorRegistry registry) { - this.registry = registry; - } - - @Override - public void increment(Delta delta) { - counter(delta.getName()).inc(delta.getValue().doubleValue()); - } - - @Override - public void reset(String metricName) { - counter(metricName).clear(); - } - - @Override - public void set(Metric value) { - gauge(value.getName()).set(value.getValue().doubleValue()); - } - - private Counter counter(String name) { - String key = sanitizeName(name); - return counters.computeIfAbsent(key, k -> Counter.build().name(k).help(k).register(registry)); - } - - private Gauge gauge(String name) { - String key = sanitizeName(name); - return gauges.computeIfAbsent(key, k -> Gauge.build().name(k).help(k).register(registry)); - } - - private String sanitizeName(String name) { - return name.replaceAll("[^a-zA-Z0-9_]", "_"); - } - -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ccb69d4..604116e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,3 +4,5 @@ endpoints.health.enabled=false spring.zipkin.baseUrl=http://${zipkin:zipkin}:9411/ spring.sleuth.sampler.percentage=1.0 spring.application.name=orders +# Disable actuator metrics endpoints +endpoints.metrics.enabled=false diff --git a/src/test/java/works/weave/socks/orders/entities/UnitPojo.java b/src/test/java/works/weave/socks/orders/entities/UnitPojo.java index c704d71..ec12386 100644 --- a/src/test/java/works/weave/socks/orders/entities/UnitPojo.java +++ b/src/test/java/works/weave/socks/orders/entities/UnitPojo.java @@ -16,7 +16,7 @@ public class UnitPojo { // Configured for expectation, so we know when a class gets added or removed. - private static final int EXPECTED_CLASS_COUNT = 7; + private static final int EXPECTED_CLASS_COUNT = 8; // The package to test private static final String POJO_PACKAGE = "works.weave.socks.orders.entities";