package alien4cloud.webconfiguration; import static com.codahale.metrics.MetricRegistry.name; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import com.codahale.metrics.Counter; import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; import com.codahale.metrics.servlet.AbstractInstrumentedFilter; import lombok.extern.slf4j.Slf4j; /** * {@link Filter} implementation which captures request information and a breakdown of the response * codes being returned. */ @Slf4j public class MetricsFilter implements Filter { private static final String NAME_PREFIX = "responseCodes."; private static final int OK = 200; private static final int CREATED = 201; private static final int NO_CONTENT = 204; private static final int BAD_REQUEST = 400; private static final int NOT_FOUND = 404; private static final int SERVER_ERROR = 500; private final String otherMetricName; private final Map<Integer, String> meterNamesByStatusCode; // initialized after call of init method private ConcurrentMap<Integer, Meter> metersByStatusCode; private Meter otherMeter; private Counter activeRequests; private Timer requestTimer; /** * Creates a new instance of the filter. */ public MetricsFilter(MetricRegistry metricsRegistry) { this.otherMetricName = NAME_PREFIX + "other"; this.meterNamesByStatusCode = createMeterNamesByStatusCode(); this.metersByStatusCode = new ConcurrentHashMap<Integer, Meter>(meterNamesByStatusCode.size()); for (Entry<Integer, String> entry : meterNamesByStatusCode.entrySet()) { metersByStatusCode.put(entry.getKey(), metricsRegistry.meter(name(AbstractInstrumentedFilter.class, entry.getValue()))); } this.otherMeter = metricsRegistry.meter(name(AbstractInstrumentedFilter.class, otherMetricName)); this.activeRequests = metricsRegistry.counter(name(AbstractInstrumentedFilter.class, "activeRequests")); this.requestTimer = metricsRegistry.timer(name(AbstractInstrumentedFilter.class, "requests")); log.info("Metrics filter initialized."); } private static Map<Integer, String> createMeterNamesByStatusCode() { final Map<Integer, String> meterNamesByStatusCode = new HashMap<Integer, String>(6); meterNamesByStatusCode.put(OK, NAME_PREFIX + "ok"); meterNamesByStatusCode.put(CREATED, NAME_PREFIX + "created"); meterNamesByStatusCode.put(NO_CONTENT, NAME_PREFIX + "noContent"); meterNamesByStatusCode.put(BAD_REQUEST, NAME_PREFIX + "badRequest"); meterNamesByStatusCode.put(NOT_FOUND, NAME_PREFIX + "notFound"); meterNamesByStatusCode.put(SERVER_ERROR, NAME_PREFIX + "serverError"); return meterNamesByStatusCode; } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { final StatusExposingServletResponse wrappedResponse = new StatusExposingServletResponse((HttpServletResponse) response); activeRequests.inc(); final Timer.Context context = requestTimer.time(); try { chain.doFilter(request, wrappedResponse); } finally { context.stop(); activeRequests.dec(); markMeterForStatusCode(wrappedResponse.getStatus()); } } private void markMeterForStatusCode(int status) { final Meter metric = metersByStatusCode.get(status); if (metric != null) { metric.mark(); } else { otherMeter.mark(); } } private static class StatusExposingServletResponse extends HttpServletResponseWrapper { // The Servlet spec says: calling setStatus is optional, if no status is set, the default is 200. private int httpStatus = 200; public StatusExposingServletResponse(HttpServletResponse response) { super(response); } @Override public void sendError(int sc) throws IOException { httpStatus = sc; super.sendError(sc); } @Override public void sendError(int sc, String msg) throws IOException { httpStatus = sc; super.sendError(sc, msg); } @Override public void setStatus(int sc) { httpStatus = sc; super.setStatus(sc); } public int getStatus() { return httpStatus; } } }