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;
}
}
}