001package io.prometheus.client.filter; 002 003import io.prometheus.client.Adapter; 004import io.prometheus.client.servlet.common.filter.Filter; 005import io.prometheus.client.servlet.common.filter.FilterConfigurationException; 006 007import javax.servlet.*; 008import javax.servlet.http.HttpServletRequest; 009import javax.servlet.http.HttpServletResponse; 010import java.io.IOException; 011 012/** 013 * The MetricsFilter class provides a high-level filter that enables tunable collection of metrics for Servlet 014 * performance. 015 * 016 * This is the Javax version of the MetricsFilter. If you are using Jakarta Servlet, there is a Jakarta version 017 * available in {@code simpleclient-servlet-jakarta}. 018 * 019 * <p>The metric name itself is required, and configured with a {@code metric-name} init parameter. 020 * 021 * <p>The help parameter, configured with the {@code help} init parameter, is not required but strongly recommended. 022 * 023 * <p>The Histogram buckets can be configured with a {@code buckets} init parameter whose value is a comma-separated 024 * list * of valid {@code double} values. If omitted, the default buckets from {@link io.prometheus.client.Histogram} 025 * are used. 026 * 027 * <p>By default, this filter will provide metrics that distinguish only 1 level deep for the request path 028 * (including servlet context path), but can be configured with the {@code path-components} init parameter. Any number 029 * provided that is less than 1 will provide the full path granularity (warning, this may affect performance). 030 * 031 * <p>The {@code strip-context-path} init parameter can be used to avoid including the leading path components which are 032 * part of the context (i.e. the folder where the servlet is deployed) so that the same project deployed under different 033 * paths can produce the same metrics. 034 * 035 * <p>HTTP statuses will be aggregated via Counter. The name for this counter will be derived from the 036 * {@code metric-name} init parameter. 037 * 038 * <pre>{@code 039 * <filter> 040 * <filter-name>prometheusFilter</filter-name> 041 * <!-- This example shows the javax version. For Jakarta you would use --> 042 * <!-- <filter-class>io.prometheus.client.filter.servlet.jakarta.MetricsFilter</filter-class> --> 043 * <filter-class>io.prometheus.client.filter.MetricsFilter</filter-class> 044 * <init-param> 045 * <param-name>metric-name</param-name> 046 * <param-value>webapp_metrics_filter</param-value> 047 * </init-param> 048 * <!-- help is optional, defaults to the message below --> 049 * <init-param> 050 * <param-name>help</param-name> 051 * <param-value>This is the help for your metrics filter</param-value> 052 * </init-param> 053 * <!-- buckets is optional, unless specified the default buckets from io.prometheus.client.Histogram are used --> 054 * <init-param> 055 * <param-name>buckets</param-name> 056 * <param-value>0.005,0.01,0.025,0.05,0.075,0.1,0.25,0.5,0.75,1,2.5,5,7.5,10</param-value> 057 * </init-param> 058 * <!-- path-components is optional, anything less than 1 (1 is the default) means full granularity --> 059 * <init-param> 060 * <param-name>path-components</param-name> 061 * <param-value>1</param-value> 062 * </init-param> 063 * <!-- strip-context-path is optional, defaults to false --> 064 * <init-param> 065 * <param-name>strip-context-path</param-name> 066 * <param-value>false</param-value> 067 * </init-param> 068 * </filter> 069 * 070 * <!-- You will most likely want this to be the first filter in the chain 071 * (therefore the first <filter-mapping> in the web.xml file), so that you can get 072 * the most accurate measurement of latency. --> 073 * <filter-mapping> 074 * <filter-name>prometheusFilter</filter-name> 075 * <url-pattern>/*</url-pattern> 076 * </filter-mapping> 077 * }</pre> 078 */ 079public class MetricsFilter implements javax.servlet.Filter { 080 081 private final Filter delegate; 082 083 public MetricsFilter() { 084 this.delegate = new Filter(); 085 } 086 087 // compatibility with 0.11.1 and older 088 public MetricsFilter( 089 String metricName, 090 String help, 091 Integer pathComponents, 092 double[] buckets) { 093 this(metricName, help, pathComponents, buckets, false); 094 } 095 096 /** 097 * See {@link Filter#Filter(String, String, Integer, double[], boolean)}. 098 */ 099 public MetricsFilter( 100 String metricName, 101 String help, 102 Integer pathComponents, 103 double[] buckets, 104 boolean stripContextPath) { 105 this.delegate = new Filter(metricName, help, pathComponents, buckets, stripContextPath); 106 } 107 108 @Override 109 public void init(FilterConfig filterConfig) throws ServletException { 110 try { 111 delegate.init(Adapter.wrap(filterConfig)); 112 } catch (FilterConfigurationException e) { 113 throw new ServletException(e); 114 } 115 } 116 117 @Override 118 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 119 if (!(servletRequest instanceof HttpServletRequest) || !(servletResponse instanceof HttpServletResponse)) { 120 filterChain.doFilter(servletRequest, servletResponse); 121 return; 122 } 123 Filter.MetricData data = delegate.startTimer(Adapter.wrap((HttpServletRequest) servletRequest)); 124 try { 125 filterChain.doFilter(servletRequest, servletResponse); 126 } finally { 127 delegate.observeDuration(data, Adapter.wrap((HttpServletResponse) servletResponse)); 128 } 129 } 130 131 @Override 132 public void destroy() { 133 } 134}