Including authenticated user info in log analyzer with a servlet filter 

Joined:
04/09/2007
Posts:
753

March 24, 2011 12:11:14    Last update: March 24, 2011 12:22:03
This is the task: your client wants to know how the web application is used. That is pretty easy. A plethora of commercial tools or any of the free log analysis tools such as analog and AWStats would fit the bill. But here's the catch: they want to know not only what pages are visited by how many people and when, but also who logged in and did what. Your application is using form based authentication and therefore, everyone is anonymous in the web access log. What to do?

This is a servlet filter that generates a web access log with authenticated user info that can be fed to log analysis tools such as analog and AWStats.

  1. Filter code (the output format is Apache combined):
    package com.example.util;
    
    import java.io.*;
    import javax.servlet.*;
    import javax.servlet.http.*;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import com.example.model.User;
    
    public class AccessLogFilter implements Filter {
        private static final Log log = LogFactory.getLog(AccessLogFilter.class);
    
        public void init(FilterConfig cfg) throws ServletException {
        }
    
        public void destroy() {
        }
    
        static class AccessLogWrapper extends HttpServletResponseWrapper {
            // workaround for servlet API 2.5, which does not provide getStatus()
    	private int statusCode = 200; // default status
    
    	private ByteArrayOutputStream bos;
    	private ServletOutputStream out;
    	private PrintWriter writer;
    
    	public AccessLogWrapper(HttpServletResponse resp) {
    	    super(resp);
    	    bos = new ByteArrayOutputStream();
    	}
    
        	@Override
    	public void setStatus(int s) {
    	    this.statusCode = s;
    	    super.setStatus(s);
    	}
    
        	@Override
    	public void sendError(int sc) throws IOException {
    	    this.statusCode = sc;
    	    super.sendError(sc);
    	}
    
    	@Override
    	public void sendError(int sc, String msg) throws IOException {
    	    this.statusCode = sc;
    	    super.sendError(sc, msg);
    	}
    
    	@Override
    	public void sendRedirect(String location) throws IOException {
    	    this.statusCode = 302;
    	    super.sendRedirect(location);
    	}
    
    	@Override
    	public ServletOutputStream getOutputStream() throws IOException {
    	    if (out == null) {
    		out = new ServletOutputStream() {
    		    @Override
    		    public void write(int b) throws IOException {
    			bos.write(b);
    		    }
    
    		    @Override
    		    public void flush() throws IOException {
    			bos.flush();
    		    }
    
    		    @Override
    		    public void close() throws IOException {
    			bos.flush();
    		    }
    		};
    	    }
    	    return out;
    	}
    
    	@Override
    	public PrintWriter getWriter() throws IOException {
    	    if (writer == null) {
    		writer = new PrintWriter(new OutputStreamWriter(
    				getOutputStream(),
    				getCharacterEncoding())) {
    		    // Tomcat doesn't flush when a JSP page finishes 
    		    // rendering, this is a workaround (Tomcat's internal 
    		    // writer auto-flushes, so there's no problem until you
    		    // provide Tomcat with a different writer).
    		    public void write(char[] cb, int off, int len) {
    			super.write(cb, off, len);
    			super.flush();
    		    }
    		};
    	    }
    	    return writer;
    	}
    
    	public int getStatus() {
    	    return this.statusCode;
    	}
    
    	public ByteArrayOutputStream getContent() {
    	    return this.bos;
    	}
        }
    
        public void doFilter(ServletRequest req,
    		         ServletResponse resp,
    			 FilterChain chain) 
    		throws IOException, ServletException {
    	ServletOutputStream out = resp.getOutputStream();
    	HttpServletRequest q = (HttpServletRequest) req;
    	AccessLogWrapper r = new AccessLogWrapper(
    				(HttpServletResponse) resp
    			     );
    
    	chain.doFilter(q, r);
    
    	String userName = "-";
    	HttpSession session = q.getSession(false);
    	if (session != null) {
    	    User user = (User) session.getAttribute("user");
    	    if (user != null) {
    		userName = user.getUserId();
    	    }
    	}
    
    	ByteArrayOutputStream content = r.getContent();
    	int contentLength = content.size();
    	r.setContentLength(contentLength);
    	content.writeTo(out);
    	out.close();
    
    	String remoteHost = q.getRemoteHost();
    	String requestString = String.format("%s %s %s", 
    				    q.getMethod(),
    				    q.getRequestURI(),
    				    q.getProtocol()
    			       );
    
    	int statusCode = r.getStatus();
    	String referer = q.getHeader("REFERER");
    	if (referer == null) { referer = ""; }
    	String userAgent = q.getHeader("User-Agent");
    
    	log.info(String.format("%1$s - %2$s [%3$td/%3$tb/%3$tY:%3$tT %3$tz] "
    	                     + "\"%4$s\" %5$d %6$d \"%7$s\" \"%8$s\"",
    	    remoteHost,
    	    userName,
    	    new Date(),
    	    requestString,
    	    statusCode,
    	    contentLength,
    	    referer,
    	    userAgent
    	));
        }
    }
    


  2. web.xml configuration:
    <filter>
        <filter-name>accessLogFilter</filter-name>
        <filter-class>com.example.util.AccessLogFilter</filter-class>
    </filter>
    
    <filter-mapping>
        <filter-name>accessLogFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    


  3. log4j configuration (example is for JBoss jboss-log4j.xml):
    <appender name="ACCESS_LOG" class="org.jboss.logging.appender.DailyRollingFileAppender">
        <param name="File" value="${jboss.server.log.dir}/access.log"/>
        <param name="Append" value="true"/>
        <param name="DatePattern" value="'.'yyyy-MM-dd"/>
    
        <layout class="org.apache.log4j.PatternLayout">
    	<param name="ConversionPattern" value="%m%n"/>
        </layout>
    </appender>
    
    <category name="com.example.util.AccessLogFilter" additivity="false">
        <priority value="INFO"/>
        <appender-ref ref="ACCESS_LOG"/>
    </category>
    

    Notice that additivity for the logger is set to false.
Share |
| Comment  | Tags