Including authenticated user info in log analyzer with a servlet filter
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.
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.
- 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 )); } }
-
web.xmlconfiguration:<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>
-
log4jconfiguration (example is for JBossjboss-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 thatadditivityfor the logger is set tofalse.