Last active
March 14, 2016 14:07
-
-
Save noslowerdna/6572c1113a1c770c95a7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.yammer.metrics.Metrics; | |
import com.yammer.metrics.core.Counter; | |
import com.yammer.metrics.core.Meter; | |
import com.yammer.metrics.core.Timer; | |
import com.yammer.metrics.core.TimerContext; | |
import com.yammer.metrics.web.WebappMetricsFilter; | |
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 org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
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.TimeUnit; | |
/** | |
* {@link Filter} implementation which captures request information and a breakdown of the response | |
* codes being returned. | |
* | |
* <p> | |
* Copied from {@link WebappMetricsFilter} and {@link com.yammer.metrics.web.DefaultWebappMetricsFilter} in metrics-web, which do | |
* not accurately track response statuses as of version 2.2.0, because {@link HttpServletResponseWrapper#setStatus(int, String)} is | |
* not intercepted, and that is the method that ultimately is getting called to set the status for the response. | |
* </p> | |
*/ | |
public class UpdatedMetricsFilter implements Filter { | |
private static final Logger LOG = LoggerFactory.getLogger(MetricsFilter.class); | |
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; | |
// Additional status codes used by our services. | |
private static final int UNAUTHORIZED = 401; | |
private static final int FORBIDDEN = 403; | |
private static final int TIMEOUT = 408; | |
private static final int CONFLICT = 409; | |
private static final int PRECONDITION_FAILED = 412; | |
private static final int SERVICE_UNAVAILABLE = 503; | |
private final Map<Integer, Meter> metersByStatusCode; | |
private final Map<StatusType, Meter> metersByStatusType; | |
private final Meter otherMeter; | |
private final Counter activeRequests; | |
private final Timer requestTimer; | |
/** | |
* Enumerated response status groupings. | |
*/ | |
private enum StatusType { | |
/** | |
* A status of 2xx indicates that the client's request was successfully received, understood, and accepted. | |
*/ | |
SUCCESSFUL, | |
/** | |
* A status of 4xx is used for cases in which the client seems to have erred. | |
*/ | |
CLIENT_ERROR, | |
/** | |
* A status of 5xx means that the server is aware that it has erred or is incapable of performing the request. | |
*/ | |
SERVER_ERROR, | |
/** | |
* Fail-safe type for any response statuses that do not map to one of the other types. | |
*/ | |
OTHER | |
} | |
/** | |
* Creates a new instance of the filter. | |
*/ | |
public UpdatedMetricsFilter() { | |
Map<Integer, String> meterNamesByStatusCode = createMeterNamesByStatusCode(); | |
this.metersByStatusCode = new ConcurrentHashMap<Integer, Meter>(meterNamesByStatusCode.size()); | |
for (Entry<Integer, String> entry : meterNamesByStatusCode.entrySet()) { | |
metersByStatusCode.put(entry.getKey(), getMeter(entry.getValue())); | |
} | |
Map<StatusType, String> meterNamesByStatusType = createMeterNamesByStatusType(); | |
this.metersByStatusType = new ConcurrentHashMap<StatusType, Meter>(meterNamesByStatusType.size()); | |
for (Entry<StatusType, String> entry : meterNamesByStatusType.entrySet()) { | |
metersByStatusType.put(entry.getKey(), getMeter(entry.getValue())); | |
} | |
this.otherMeter = getMeter(NAME_PREFIX + "other"); | |
this.activeRequests = Metrics.newCounter(WebappMetricsFilter.class, "activeRequests"); | |
this.requestTimer = Metrics.newTimer(WebappMetricsFilter.class, | |
"requests", | |
TimeUnit.MILLISECONDS, | |
TimeUnit.SECONDS); | |
} | |
private static Meter getMeter(String name) { | |
return Metrics.newMeter(WebappMetricsFilter.class, name, "responses", TimeUnit.SECONDS); | |
} | |
private static Map<Integer, String> createMeterNamesByStatusCode() { | |
final Map<Integer, String> meterNamesByStatusCode = new HashMap<Integer, String>(); | |
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"); | |
meterNamesByStatusCode.put(UNAUTHORIZED, NAME_PREFIX + "unauthorized"); | |
meterNamesByStatusCode.put(FORBIDDEN, NAME_PREFIX + "forbidden"); | |
meterNamesByStatusCode.put(TIMEOUT, NAME_PREFIX + "timeout"); | |
meterNamesByStatusCode.put(CONFLICT, NAME_PREFIX + "conflict"); | |
meterNamesByStatusCode.put(PRECONDITION_FAILED, NAME_PREFIX + "preconditionFailed"); | |
meterNamesByStatusCode.put(SERVICE_UNAVAILABLE, NAME_PREFIX + "serviceUnavailable"); | |
return meterNamesByStatusCode; | |
} | |
private static Map<StatusType, String> createMeterNamesByStatusType() { | |
final Map<StatusType, String> meterNamesByStatusType = new HashMap<StatusType, String>(); | |
meterNamesByStatusType.put(StatusType.SUCCESSFUL, NAME_PREFIX + "2xx"); | |
meterNamesByStatusType.put(StatusType.CLIENT_ERROR, NAME_PREFIX + "4xx"); | |
meterNamesByStatusType.put(StatusType.SERVER_ERROR, NAME_PREFIX + "5xx"); | |
return meterNamesByStatusType; | |
} | |
@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 TimerContext context = requestTimer.time(); | |
try { | |
chain.doFilter(request, wrappedResponse); | |
} finally { | |
context.stop(); | |
activeRequests.dec(); | |
markMeterForStatusCode(wrappedResponse.getStatus()); | |
} | |
} | |
private void markMeterForStatusCode(int status) { | |
final Meter statusCodeMeter = metersByStatusCode.get(status); | |
if (statusCodeMeter != null) { | |
statusCodeMeter.mark(); | |
} else { | |
otherMeter.mark(); | |
} | |
final Meter statusTypeMeter = metersByStatusType.get(getStatusType(status)); | |
if (statusTypeMeter != null) { | |
statusTypeMeter.mark(); | |
} | |
} | |
private static StatusType getStatusType(int status) { | |
switch (status / 100) { | |
case 2: | |
// 2xx | |
return StatusType.SUCCESSFUL; | |
case 4: | |
// 4xx | |
return StatusType.CLIENT_ERROR; | |
case 5: | |
// 5xx | |
return StatusType.SERVER_ERROR; | |
default: | |
// Anything else is very unlikely. | |
LOG.debug("Unexpected status outside of the standard 2xx/4xx/5xx groups: {}", status); | |
return StatusType.OTHER; | |
} | |
} | |
private static class StatusExposingServletResponse extends HttpServletResponseWrapper { | |
private int httpStatus; | |
/** | |
* Creates a new status-exposing response wrapper instance. | |
* | |
* @param response the wrapped response | |
*/ | |
public StatusExposingServletResponse(HttpServletResponse response) { | |
super(response); | |
// Defaults to 200 if not set, in compliance with the servlet specification. | |
this.httpStatus = 200; | |
} | |
@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); | |
} | |
@Override | |
public void setStatus(int sc, String sm) { | |
httpStatus = sc; | |
super.setStatus(sc, sm); | |
} | |
/** | |
* Returns the intercepted response status code. | |
* | |
* @return the status code of the response. | |
*/ | |
public int getStatus() { | |
return httpStatus; | |
} | |
} | |
} |
Author
noslowerdna
commented
Mar 14, 2016
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment