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