001/** 002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.jboot.support.metric.request; 017 018import com.codahale.metrics.Counter; 019import com.codahale.metrics.Meter; 020import com.codahale.metrics.MetricRegistry; 021import com.codahale.metrics.Timer; 022import com.jfinal.handler.Handler; 023import io.jboot.Jboot; 024import io.jboot.support.metric.JbootMetricConfig; 025 026import javax.servlet.*; 027import javax.servlet.http.HttpServletRequest; 028import javax.servlet.http.HttpServletResponse; 029import javax.servlet.http.HttpServletResponseWrapper; 030import java.io.IOException; 031import java.util.Map; 032import java.util.Map.Entry; 033import java.util.concurrent.ConcurrentHashMap; 034import java.util.concurrent.ConcurrentMap; 035 036import static com.codahale.metrics.MetricRegistry.name; 037 038/** 039 * {@link Filter} implementation which captures request information and a breakdown of the response 040 * codes being returned. 041 */ 042public abstract class AbstractInstrumentedFilter extends Handler { 043 044 private final Map<Integer, String> meterNamesByStatusCode; 045 046 // initialized after call of init method 047 private ConcurrentMap<Integer, Meter> metersByStatusCode; 048 private Meter otherMeter; 049 private Meter timeoutsMeter; 050 private Meter errorsMeter; 051 private Counter activeRequests; 052 private Timer requestTimer; 053 054 private JbootMetricConfig jbootMetricConfig = Jboot.config(JbootMetricConfig.class); 055 056 057 /** 058 * Creates a new instance of the filter. 059 * 060 * @param meterNamesByStatusCode A map, keyed by status code, of meter names that we are 061 * interested in. 062 * @param otherMetricName The name used for the catch-all meter. 063 */ 064 protected AbstractInstrumentedFilter(Map<Integer, String> meterNamesByStatusCode, String otherMetricName) { 065 this.meterNamesByStatusCode = meterNamesByStatusCode; 066 067 068 final MetricRegistry metricsRegistry = Jboot.getMetric(); 069 070 String metricName = jbootMetricConfig.getRequestMetricName(); 071 if (metricName == null || metricName.isEmpty()) { 072 metricName = getClass().getName(); 073 } 074 075 this.metersByStatusCode = new ConcurrentHashMap<>(meterNamesByStatusCode.size()); 076 for (Entry<Integer, String> entry : meterNamesByStatusCode.entrySet()) { 077 metersByStatusCode.put(entry.getKey(), 078 metricsRegistry.meter(name(metricName, entry.getValue()))); 079 } 080 this.otherMeter = metricsRegistry.meter(name(metricName, otherMetricName)); 081 this.timeoutsMeter = metricsRegistry.meter(name(metricName, "timeouts")); 082 this.errorsMeter = metricsRegistry.meter(name(metricName, "errors")); 083 this.activeRequests = metricsRegistry.counter(name(metricName, "activeRequests")); 084 this.requestTimer = metricsRegistry.timer(name(metricName, "requests")); 085 086 } 087 088 089 @Override 090 public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) { 091 092 final StatusExposingServletResponse wrappedResponse = new StatusExposingServletResponse(response); 093 activeRequests.inc(); 094 final Timer.Context context = requestTimer.time(); 095 boolean error = false; 096 try { 097 next.handle(target, request, wrappedResponse, isHandled); 098 } catch (Exception e) { 099 error = true; 100 throw e; 101 } finally { 102 if (!error && request.isAsyncStarted()) { 103 request.getAsyncContext().addListener(new AsyncResultListener(context)); 104 } else { 105 context.stop(); 106 activeRequests.dec(); 107 if (error) { 108 errorsMeter.mark(); 109 } else { 110 markMeterForStatusCode(wrappedResponse.getStatus()); 111 } 112 } 113 } 114 } 115 116 private void markMeterForStatusCode(int status) { 117 final Meter metric = metersByStatusCode.get(status); 118 if (metric != null) { 119 metric.mark(); 120 } else { 121 otherMeter.mark(); 122 } 123 } 124 125 private static class StatusExposingServletResponse extends HttpServletResponseWrapper { 126 // The Servlet spec says: calling setStatus is optional, if no status is set, the default is 200. 127 private int httpStatus = 200; 128 129 public StatusExposingServletResponse(HttpServletResponse response) { 130 super(response); 131 } 132 133 @Override 134 public void sendError(int sc) throws IOException { 135 httpStatus = sc; 136 super.sendError(sc); 137 } 138 139 @Override 140 public void sendError(int sc, String msg) throws IOException { 141 httpStatus = sc; 142 super.sendError(sc, msg); 143 } 144 145 @Override 146 public void setStatus(int sc) { 147 httpStatus = sc; 148 super.setStatus(sc); 149 } 150 151 @Override 152 @SuppressWarnings("deprecation") 153 public void setStatus(int sc, String sm) { 154 httpStatus = sc; 155 super.setStatus(sc, sm); 156 } 157 158 @Override 159 public int getStatus() { 160 return httpStatus; 161 } 162 } 163 164 private class AsyncResultListener implements AsyncListener { 165 private Timer.Context context; 166 private boolean done = false; 167 168 public AsyncResultListener(Timer.Context context) { 169 this.context = context; 170 } 171 172 @Override 173 public void onComplete(AsyncEvent event) throws IOException { 174 if (!done) { 175 HttpServletResponse suppliedResponse = (HttpServletResponse) event.getSuppliedResponse(); 176 context.stop(); 177 activeRequests.dec(); 178 markMeterForStatusCode(suppliedResponse.getStatus()); 179 } 180 } 181 182 @Override 183 public void onTimeout(AsyncEvent event) throws IOException { 184 context.stop(); 185 activeRequests.dec(); 186 timeoutsMeter.mark(); 187 done = true; 188 } 189 190 @Override 191 public void onError(AsyncEvent event) throws IOException { 192 context.stop(); 193 activeRequests.dec(); 194 errorsMeter.mark(); 195 done = true; 196 } 197 198 @Override 199 public void onStartAsync(AsyncEvent event) throws IOException { 200 201 } 202 } 203}