When implementing a (JSON) REST server interface you can not control what content/structure the client send. Maybe you have modified your data model on the server and client is not informed about this or the client send a broken data structure. When the client send a broken data structure to the server the server throws an Exception with a message like 'Illegal unquoted character'.
With the following code snippets you can implement a logging for JSON REST calls:
First you have to build a webfilter around the RESTeasy calls.
The tricky thing is here to use a special ServletRequest who overrides the normal HttpServletRequest. This is necessary because of the payload of the service is a input stream. To process the payload two times the input stream is first stored in a StringBuffer, so the payload can often be called. One to process the normal JSON mapping process, the second time to log the payload.
package com.sourcecoding.mr678.webservice; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import javax.ejb.EJB; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import com.sourcecoding.mr678.model.RESTLogger; import com.sourcecoding.mr678.service.PersistenceService; @WebFilter(servletNames = { "javax.ws.rs.core.Application" }) public class RESTFilter implements Filter { private static final Logger LOG = Logger.getLogger(RESTFilter.class.getName()); @EJB private PersistenceService persistenceService; @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { LoggingRequest loggingRequest = new LoggingRequest((HttpServletRequest) req); chain.doFilter(loggingRequest, resp); LoggingInputStream lis = ((LoggingInputStream) loggingRequest.getInputStream()); RESTLogger rl = new RESTLogger(); rl.setUrl(((HttpServletRequest) req).getRequestURI()); // FIXME auf 4000 Zeichen beschränkt - bekommt sogar exception rl.setPayload(lis.getPayload()); rl.setOk(true); if (HttpServletResponse.SC_BAD_REQUEST == ((HttpServletResponse) resp).getStatus()) { rl.setOk(false); rl.setErrorMessage(lis.getException().getMessage()); LOG.log(Level.WARNING, "REST-Call: " + lis.getException().getMessage()); LOG.log(Level.WARNING, "Payload beim Fehler: \n" + lis.getPayload()); } try { persistenceService.create(rl); } catch (Exception e) { LOG.severe(e.getMessage()); e.printStackTrace(); } } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void destroy() { } private class LoggingRequest extends HttpServletRequestWrapper { private LoggingInputStream is; public LoggingRequest(HttpServletRequest request) throws IOException { super(request); this.is = new LoggingInputStream(request.getInputStream()); } @Override public ServletInputStream getInputStream() throws IOException { return is; } } protected class LoggingInputStream extends ServletInputStream { private StringBuffer bfr = new StringBuffer(); private ServletInputStream is; private Exception exception; public LoggingInputStream(ServletInputStream is) { super(); this.is = is; } @Override public int read() throws IOException { int ch = is.read(); if (ch != -1) { bfr.append((char) ch); } return ch; } @Override public int read(byte[] b) throws IOException { int ch = is.read(b); if (ch != -1) { for (byte byteSingle : b) bfr.append((char) byteSingle); } return ch; } @Override public int read(byte[] b, int o, int l) throws IOException { int ch = is.read(b, o, l); if (ch != -1) { for (byte byteSingle : b) bfr.append((char) byteSingle); } return ch; } @Override public int readLine(byte[] b, int o, int l) throws IOException { int ch = is.readLine(b, o, l); if (ch != -1) { bfr.append(b); } return ch; } public String getPayload() { return bfr.toString(); } public Exception getException() { return exception; } public void setException(Exception exception) { this.exception = exception; } } }
To track the exception more exactly a special ExceptionMapper have to implemented.
package de.sourcecoding.mr678.webservice; import java.util.logging.Level; import java.util.logging.Logger; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; import org.codehaus.jackson.JsonProcessingException; import com.sourcecoding.mr678.RESTFilter.LoggingInputStream; @Provider public class RESTServiceExceptionMapper implements ExceptionMapper<JsonProcessingException> { private static final Logger LOG = Logger .getLogger(RESTServiceExceptionMapper.class.getName()); @Override public Response toResponse(JsonProcessingException exception) { LOG.log(Level.SEVERE, exception.getMessage()); Object o = exception.getLocation().getSourceRef(); if (o instanceof LoggingInputStream) { ((LoggingInputStream) o).setException(exception); } return Response.status(Response.Status.BAD_REQUEST).entity(exception.getMessage()) .type("text/plain").build(); } }