diff --git a/pom.xml b/pom.xml index e94b7a75d..eac7c9695 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 @@ -36,6 +37,21 @@ spring-cloud-starter-zipkin 1.1.0.RELEASE + + io.prometheus + simpleclient_spring_boot + ${prometheus.version} + + + io.prometheus + simpleclient_hotspot + ${prometheus.version} + + + io.prometheus + simpleclient_servlet + ${prometheus.version} + org.springframework.data spring-data-rest-hal-browser diff --git a/src/main/java/works/weave/socks/cart/configuration/PrometheusAutoConfiguration.java b/src/main/java/works/weave/socks/cart/configuration/PrometheusAutoConfiguration.java new file mode 100644 index 000000000..00420de9b --- /dev/null +++ b/src/main/java/works/weave/socks/cart/configuration/PrometheusAutoConfiguration.java @@ -0,0 +1,35 @@ +package works.weave.socks.cart.configuration; + +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/cart/configuration/WebMvcConfig.java b/src/main/java/works/weave/socks/cart/configuration/WebMvcConfig.java new file mode 100644 index 000000000..a6e7eded8 --- /dev/null +++ b/src/main/java/works/weave/socks/cart/configuration/WebMvcConfig.java @@ -0,0 +1,25 @@ +package works.weave.socks.cart.configuration; + +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.cart.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/cart/middleware/HTTPMonitoringInterceptor.java b/src/main/java/works/weave/socks/cart/middleware/HTTPMonitoringInterceptor.java new file mode 100644 index 000000000..36c68da44 --- /dev/null +++ b/src/main/java/works/weave/socks/cart/middleware/HTTPMonitoringInterceptor.java @@ -0,0 +1,48 @@ +package works.weave.socks.cart.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:carts}") + 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/resources/application.properties b/src/main/resources/application.properties index 35146f238..855a29ee6 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=carts +# Disable actuator metrics endpoints +endpoints.metrics.enabled=false