001package nl.nlighten.prometheus.wildfly; 002 003import io.prometheus.client.*; 004 005import javax.servlet.Filter; 006import javax.servlet.FilterChain; 007import javax.servlet.FilterConfig; 008import javax.servlet.ServletException; 009import javax.servlet.ServletRequest; 010import javax.servlet.ServletResponse; 011import javax.servlet.http.HttpServletRequest; 012import javax.servlet.http.HttpServletResponse; 013import java.io.IOException; 014 015/** 016 * A servlet filter that will be added to deployed servlets by the MetricFilterExtension that provides the following metrics: 017 * <p> 018 * - A Histogram with response time distribution per context 019 * - A Gauge with the number of concurrent request per context 020 * - A Gauge with a the number of responses per context and status code 021 * </p> 022 * Example metrics being exported: 023 * <pre> 024 * servlet_request_seconds_bucket{"/foo", "GET", "0.1",} 1.0 025 * .... 026 * servlet_request_seconds_bucket{"/foo", "GET", "+Inf",} 1.0 027 * servlet_request_concurrent_total{"/foo",} 1.0 028 * servlet_response_status_total{"/foo", "200",} 1.0 029 * </pre> 030 */ 031public class ServletMetricsFilter implements Filter { 032 public static final String BUCKET_CONFIG_PARAM = "buckets"; 033 private static Histogram servletLatency; 034 private static Gauge servletConcurrentRequest; 035 private static Gauge servletStatusCodes; 036 037 private static int UNDEFINED_HTTP_STATUS = 999; 038 039 @Override 040 public void init(FilterConfig filterConfig) throws ServletException { 041 if (servletLatency == null) { 042 Histogram.Builder servletLatencyBuilder = Histogram.build() 043 .name("servlet_request_seconds") 044 .help("The time taken fulfilling servlet requests") 045 .labelNames("context", "method"); 046 047 if ((filterConfig.getInitParameter(BUCKET_CONFIG_PARAM) != null) && (!filterConfig.getInitParameter(BUCKET_CONFIG_PARAM).isEmpty())) { 048 String[] bucketParams = filterConfig.getInitParameter(BUCKET_CONFIG_PARAM).split(","); 049 double[] buckets = new double[bucketParams.length]; 050 for (int i = 0; i < bucketParams.length; i++) { 051 buckets[i] = Double.parseDouble(bucketParams[i].trim()); 052 } 053 servletLatencyBuilder.buckets(buckets); 054 } else { 055 servletLatencyBuilder.buckets(.01, .05, .1, .25, .5, 1, 2.5, 5, 10, 30); 056 } 057 058 servletLatency = servletLatencyBuilder.register(); 059 060 Gauge.Builder servletConcurrentRequestBuilder = Gauge.build() 061 .name("servlet_request_concurrent_total") 062 .help("Number of concurrent requests for given context.") 063 .labelNames("context"); 064 065 servletConcurrentRequest = servletConcurrentRequestBuilder.register(); 066 067 Gauge.Builder servletStatusCodesBuilder = Gauge.build() 068 .name("servlet_response_status_total") 069 .help("Number of requests for given context and status code.") 070 .labelNames("context", "status"); 071 072 servletStatusCodes = servletStatusCodesBuilder.register(); 073 074 } 075 } 076 077 @Override 078 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 079 if (!(servletRequest instanceof HttpServletRequest)) { 080 filterChain.doFilter(servletRequest, servletResponse); 081 return; 082 } 083 084 HttpServletRequest request = (HttpServletRequest) servletRequest; 085 086 if (!request.isAsyncStarted()) { 087 String context = getContext(request); 088 089 servletConcurrentRequest.labels(context).inc(); 090 091 Histogram.Timer timer = servletLatency 092 .labels(context, request.getMethod()) 093 .startTimer(); 094 095 try { 096 filterChain.doFilter(servletRequest, servletResponse); 097 } finally { 098 timer.observeDuration(); 099 servletConcurrentRequest.labels(context).dec(); 100 servletStatusCodes.labels(context, Integer.toString(getStatus((HttpServletResponse) servletResponse))).inc(); 101 } 102 } else { 103 filterChain.doFilter(servletRequest, servletResponse); 104 } 105 } 106 107 private int getStatus(HttpServletResponse response) { 108 try { 109 return response.getStatus(); 110 } catch (Exception ex) { 111 return UNDEFINED_HTTP_STATUS; 112 } 113 } 114 115 private String getContext(HttpServletRequest request) { 116 if (request.getContextPath() != null && !request.getContextPath().isEmpty()) { 117 return request.getContextPath(); 118 } else { 119 return "/"; 120 } 121 } 122 123 @Override 124 public void destroy() { 125 // NOOP 126 } 127}