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}