Skip navigation
2012

Matthias Reining's Blog

March 2012 Previous month Next month
mr-678

RESTeasy - Troublelogger

Posted by mr-678 Mar 1, 2012

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

Filter Blog