001package io.prometheus.client.servlet.jakarta.filter; 002 003import io.prometheus.client.servlet.jakarta.Adapter; 004import io.prometheus.client.servlet.common.filter.Filter; 005import io.prometheus.client.servlet.common.filter.FilterConfigurationException; 006 007import jakarta.servlet.*; 008import jakarta.servlet.http.HttpServletRequest; 009import jakarta.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 Jakarta version of the MetricsFilter. If you are using Javax Servlet, there is a Javax version 017 * available in {@code simpleclient-servlet}. 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 jakarta.servlet.Filter { 080 081 private final Filter delegate; 082 083 public MetricsFilter() { 084 this.delegate = new Filter(); 085 } 086 087 /** 088 * See {@link Filter#Filter(String, String, Integer, double[], boolean)}. 089 */ 090 public MetricsFilter( 091 String metricName, 092 String help, 093 Integer pathComponents, 094 double[] buckets, 095 boolean stripContextPath) { 096 this.delegate = new Filter(metricName, help, pathComponents, buckets, stripContextPath); 097 } 098 099 @Override 100 public void init(FilterConfig filterConfig) throws ServletException { 101 try { 102 delegate.init(Adapter.wrap(filterConfig)); 103 } catch (FilterConfigurationException e) { 104 throw new ServletException(e); 105 } 106 } 107 108 @Override 109 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 110 if (!(servletRequest instanceof HttpServletRequest) || !(servletResponse instanceof HttpServletResponse)) { 111 filterChain.doFilter(servletRequest, servletResponse); 112 return; 113 } 114 Filter.MetricData data = delegate.startTimer(Adapter.wrap((HttpServletRequest) servletRequest)); 115 try { 116 filterChain.doFilter(servletRequest, servletResponse); 117 } finally { 118 delegate.observeDuration(data, Adapter.wrap((HttpServletResponse) servletResponse)); 119 } 120 } 121 122 @Override 123 public void destroy() { 124 } 125}