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