package me.chanjar.tomcat.valves; import java.io.IOException; import java.net.InetAddress; import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Scanner; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpSession; import org.apache.catalina.AccessLog; import org.apache.catalina.Globals; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleState; import org.apache.catalina.Session; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.apache.catalina.util.RequestUtil; import org.apache.catalina.valves.Constants; import org.apache.catalina.valves.ValveBase; import org.apache.coyote.RequestInfo; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.res.StringManager; import com.mongodb.BasicDBObject; import com.mongodb.DBCollection; import com.mongodb.DBObject; import com.mongodb.WriteConcern; /** * Documentation: <a href="https://github.com/chanjarster/tomcat-mongo-access-log">Github</a> * @author Dianiel Qian(chanjarster@gmail.com) * */ public class MongoAccessLogValve extends ValveBase implements AccessLog { private static final Log log = LogFactory.getLog(MongoAccessLogValve.class); /** * The string manager for this package. */ protected static final StringManager sm = StringManager.getManager(me.chanjar.tomcat.valves.Constants.Package); //------------------------------------------------------ Constructor public MongoAccessLogValve() { super(true); } // ----------------------------------------------------- Instance Variables /** * The descriptive information about this implementation. */ protected static final String info = "chanjarster.tomcat.valves.MongoAccessLogValve/0.1"; /** * The pattern used to format our access log lines. */ protected String pattern = null; /** * The system time when we last updated the Date that this valve * uses for log lines. */ private static final ThreadLocal<Date> localDate = new ThreadLocal<Date>() { @Override protected Date initialValue() { return new Date(); } }; /** * Resolve hosts. */ private boolean resolveHosts = false; /** * Are we doing conditional logging. default null. * It is the value of <code>conditionUnless</code> property. */ protected String condition = null; /** * Are we doing conditional logging. default null. * It is the value of <code>conditionIf</code> property. */ protected String conditionIf = null; /** * Don't log the request matches the patterns */ protected String excludes = ".js,.css,jpg,.jpeg,.gif,.png,.bmp,.gif,.html,.htm"; protected String[] excludePatterns = {".js", ".css", ".jpeg", ".jpg", ".gif", ".png", ".bmp", ".gif", ".html", ".htm"}; /** * MongoDB uri. See <a>http://api.mongodb.org/java/current/com/mongodb/MongoClientURI.html</a> */ protected String uri; /** * Which db to store access logs. Default is tomcat */ protected String dbName = "tomcat"; /** * Which Collection to store access logs. Default is tomcat_access_logs */ protected String collName = "tomcat_access_logs"; /** * Should we rotate our log collection? Default is true (like old behavior) * if true then use capped collection */ protected boolean rotatable = true; /** * MongoDB collection's max size(in megabytes), Default is 1024 */ protected int capSize = 1024; /** * MongoDB collection instance */ protected DBCollection coll = null; /** * Array of AccessLogElement, they will be used to make log message. */ protected AccessLogElement[] logElements = null; /** * @see #setRequestAttributesEnabled(boolean) */ protected boolean requestAttributesEnabled = false; /** * If true, then record the error page */ protected boolean recordError = true; // ------------------------------------------------------------- Properties /** * {@inheritDoc} */ @Override public void setRequestAttributesEnabled(boolean requestAttributesEnabled) { this.requestAttributesEnabled = requestAttributesEnabled; } /** * {@inheritDoc} */ @Override public boolean getRequestAttributesEnabled() { return requestAttributesEnabled; } /** * Return descriptive information about this implementation. */ @Override public String getInfo() { return (info); } /** * Return the format pattern. */ public String getPattern() { return (this.pattern); } /** * Set the format pattern, first translating any recognized alias. * * @param pattern The new pattern */ public void setPattern(String pattern) { if (pattern == null) { this.pattern = ""; } else if (pattern.equals(Constants.AccessLog.COMMON_ALIAS)) { this.pattern = Constants.AccessLog.COMMON_PATTERN; } else if (pattern.equals(Constants.AccessLog.COMBINED_ALIAS)) { this.pattern = Constants.AccessLog.COMBINED_PATTERN; } else if (pattern.equals(me.chanjar.tomcat.valves.Constants.MongoAccessLog.DEFAULT_ALIAS)) { this.pattern = me.chanjar.tomcat.valves.Constants.MongoAccessLog.DEFAULT_PATTERN; } else if (pattern.equals(me.chanjar.tomcat.valves.Constants.MongoAccessLog.ALL_ALIAS)) { this.pattern = me.chanjar.tomcat.valves.Constants.MongoAccessLog.ALL_PATTERN; } else { this.pattern = pattern; } logElements = createLogElements(); } /** * Should we rotate the logs */ public boolean isRotatable() { return rotatable; } /** * Set the value is we should we rotate the logs * * @param rotatable true is we should rotate. */ public void setRotatable(boolean rotatable) { this.rotatable = rotatable; } /** * Set the resolve hosts flag. * * @param resolveHosts The new resolve hosts value * @deprecated Unused, removed in Tomcat 8. * See org.apache.catalina.connector.Connector.setEnableLookups(boolean). */ @Deprecated public void setResolveHosts(boolean resolveHosts) { this.resolveHosts = resolveHosts; } /** * Get the value of the resolve hosts flag. * @deprecated Unused, removed in Tomcat 8. * See org.apache.catalina.connector.Connector.setEnableLookups(boolean). */ @Deprecated public boolean isResolveHosts() { return resolveHosts; } /** * Return whether the attribute name to look for when * performing conditional logging. If null, every * request is logged. */ public String getCondition() { return condition; } /** * Set the ServletRequest.attribute to look for to perform * conditional logging. Set to null to log everything. * * @param condition Set to null to log everything */ public void setCondition(String condition) { this.condition = condition; } /** * Return whether the attribute name to look for when * performing conditional logging. If null, every * request is logged. */ public String getConditionUnless() { return getCondition(); } /** * Set the ServletRequest.attribute to look for to perform * conditional logging. Set to null to log everything. * * @param condition Set to null to log everything */ public void setConditionUnless(String condition) { setCondition(condition); } /** * Return whether the attribute name to look for when * performing conditional logging. If null, every * request is logged. */ public String getConditionIf() { return conditionIf; } /** * Set the ServletRequest.attribute to look for to perform * conditional logging. Set to null to log everything. * * @param condition Set to null to log everything */ public void setConditionIf(String condition) { this.conditionIf = condition; } // --------------------------------------------------------- Public Methods /** * Execute a periodic task, such as reloading, etc. This method will be * invoked inside the classloading context of this container. Unexpected * throwables will be caught and logged. */ @Override public synchronized void backgroundProcess() { // do nothing } /** * Log a message summarizing the specified request and response, according * to the format specified by the <code>pattern</code> property. * * @param request Request being processed * @param response Response being processed * * @exception IOException if an input/output error has occurred * @exception ServletException if a servlet error has occurred */ @Override public void invoke(Request request, Response response) throws IOException, ServletException { getNext().invoke(request, response); } @Override public void log(Request request, Response response, long time) { if (!getState().isAvailable() || logElements == null || condition != null && null != request.getRequest().getAttribute(condition) || conditionIf != null && null == request.getRequest().getAttribute(conditionIf)) { return; } String uri = request.getRequestURI(); if (uri != null) { for(String pattern : this.excludePatterns) { if (uri.indexOf(pattern) != -1) { return; } } } /** * XXX This is a bit silly, but we want to have start and stop time and * duration consistent. It would be better to keep start and stop * simply in the request and/or response object and remove time * (duration) from the interface. */ long start = request.getCoyoteRequest().getStartTime(); Date date = getDate(start + time); StringBuilder buf = new StringBuilder(); DBObject result = new BasicDBObject(10); for (int i = 0; i < logElements.length; i++) { logElements[i].addElement(buf, result, date, request, response, time); } log(result); } // -------------------------------------------------------- Private Methods /** * Close the currently open mongodb connection (if any) * */ private synchronized void close() { if (this.coll == null) { return; } try { this.coll.getDB().getMongo().close(); } catch (Exception ex) { log.error(sm.getString("mongoAccessLogValve.closeConnectionError"), ex); } finally { this.coll = null; } } /** * Log the specified message to the log file, switching files if the date * has changed since the previous log call. * * @param message Message to be logged */ public void log(DBObject log) { /* In case something external rotated the file instead */ synchronized (this) { open(); } this.coll.insert(log, WriteConcern.UNACKNOWLEDGED); } /** * Open the new log file for the date specified by <code>dateStamp</code>. */ protected synchronized void open() { if (coll != null) { return; } this.coll = CollectionFactory.getOrCreateCollection(this.uri, this.dbName, this.collName, this.rotatable, this.capSize, this.log, this.sm); } /** * This method returns a ThreadLocal Date object that is set to the * specified time. This saves creating a new Date object for every request. * * @return Date */ private static Date getDate(long systime) { Date date = localDate.get(); date.setTime(systime); return date; } /** * Find a locale by name */ protected static Locale findLocale(String name, Locale fallback) { if (name == null || name.isEmpty()) { return Locale.getDefault(); } else { for (Locale l: Locale.getAvailableLocales()) { if (name.equals(l.toString())) { return(l); } } } log.error(sm.getString("mongoAccessLogValve.invalidLocale", name)); return fallback; } /** * Start this component and implement the requirements * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}. * * @exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ @Override protected synchronized void startInternal() throws LifecycleException { open(); setState(LifecycleState.STARTING); } /** * Stop this component and implement the requirements * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}. * * @exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ @Override protected synchronized void stopInternal() throws LifecycleException { setState(LifecycleState.STOPPING); close(); } /** * AccessLogElement writes the partial message into the buffer. */ protected interface AccessLogElement { public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time); } /** * write thread name - %I { "thread" : "xxx" } */ protected static class ThreadNameElement implements AccessLogElement { @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { RequestInfo info = request.getCoyoteRequest().getRequestProcessor(); if(info != null) { result.put("thread", info.getWorkerThreadName()); } else { result.put("thread","-"); } } } /** * write local IP address - %A { "localIP" : "xxx" } */ protected static class LocalAddrElement implements AccessLogElement { private static final String LOCAL_ADDR_VALUE; static { String init; try { init = InetAddress.getLocalHost().getHostAddress(); } catch (Throwable e) { ExceptionUtils.handleThrowable(e); init = "127.0.0.1"; } LOCAL_ADDR_VALUE = init; } @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { result.put("localIP", LOCAL_ADDR_VALUE); } } /** * write remote IP address - %a { "remoteIP" : "xxx" } */ protected class RemoteAddrElement implements AccessLogElement { @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { if (requestAttributesEnabled) { Object addr = request.getAttribute(REMOTE_ADDR_ATTRIBUTE); if (addr == null) { result.put("remoteIP", request.getRemoteAddr()); } else { result.put("remoteIP", addr); } } else { result.put("remoteIP", request.getRemoteAddr()); } } } /** * write remote host name - %h { "remoteHost" : "xxx" } */ protected class HostElement implements AccessLogElement { @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { String value = null; if (requestAttributesEnabled) { Object host = request.getAttribute(REMOTE_HOST_ATTRIBUTE); if (host != null) { value = host.toString(); } } if (value == null || value.length() == 0) { value = request.getRemoteHost(); } if (value == null || value.length() == 0) { value = "-"; } result.put("remoteHost", value); } } /** * write remote logical username from identd (always returns '-') - %l { "user" : "xxx" } */ protected static class LogicalUserNameElement implements AccessLogElement { @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { result.put("user", '-'); } } /** * write request protocol - %H { "protocol" : "xxx" } */ protected class ProtocolElement implements AccessLogElement { @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { if (requestAttributesEnabled) { Object proto = request.getAttribute(PROTOCOL_ATTRIBUTE); if (proto == null) { result.put("protocol", request.getProtocol()); } else { result.put("protocol", proto); } } else { result.put("protocol", request.getProtocol()); } } } /** * write remote user that was authenticated (if any), else '-' - %u { "remoteUser" : "xxx" } */ protected static class UserElement implements AccessLogElement { @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { if (request != null) { String value = request.getRemoteUser(); if (value != null) { result.put("remoteUser", value); } else { result.put("remoteUser", '-'); } } else { result.put("remoteUser", '-'); } } } /** * write date and time - %t or %t{format}, { "datetime" : "xxx" } */ protected class DateAndTimeElement implements AccessLogElement { /* Whether to use begin of request or end of response as the timestamp */ private boolean usesBegin = false; protected DateAndTimeElement() { this(null); } protected DateAndTimeElement(String header) { } @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { long timestamp = date.getTime(); if (usesBegin) { timestamp -= time; } result.put("datetime", new Date(timestamp)); } } /** * write first line of the request (method and request URI) - %r { "line1st" : "xxx" } */ protected static class RequestElement implements AccessLogElement { @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { if (request != null) { String method = request.getMethod(); if (method == null) { // No method means no request line buf.append('-'); } else { buf.append(request.getMethod()); buf.append(' '); buf.append(request.getRequestURI()); if (request.getQueryString() != null) { buf.append('?'); buf.append(request.getQueryString()); } buf.append(' '); buf.append(request.getProtocol()); } } else { buf.append('-'); } result.put("line1st", buf.toString()); buf.delete(0, buf.length()); } } /** * write HTTP status code of the response - %s { "statusCode" : "xxx" } */ protected static class HttpStatusCodeElement implements AccessLogElement { @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { if (response != null) { result.put("statusCode", response.getStatus()); } else { result.put("statusCode", '-'); } } } /** * write local port on which this request was received - %p { "localPort" : xxx } */ protected class LocalPortElement implements AccessLogElement { @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { if (requestAttributesEnabled) { Object port = request.getAttribute(SERVER_PORT_ATTRIBUTE); if (port == null) { result.put("localPort", request.getServerPort()); } else { result.put("localPort", port); } } else { result.put("localPort", request.getServerPort()); } } } /** * write bytes sent, excluding HTTP headers - %b, %B { "bytesSent" : xxx } */ protected static class ByteSentElement implements AccessLogElement { private final boolean conversion; /** * if conversion is true, write '-' instead of 0 - %b */ public ByteSentElement(boolean conversion) { this.conversion = conversion; } @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { // Don't need to flush since trigger for log message is after the // response has been committed long length = response.getBytesWritten(false); if (length <= 0) { // Protect against nulls and unexpected types as these values // may be set by untrusted applications Object start = request.getAttribute( Globals.SENDFILE_FILE_START_ATTR); if (start instanceof Long) { Object end = request.getAttribute( Globals.SENDFILE_FILE_END_ATTR); if (end instanceof Long) { length = ((Long) end).longValue() - ((Long) start).longValue(); } } } if (length <= 0 && conversion) { result.put("bytesSent", '-'); } else { result.put("bytesSent", length); } } } /** * write request method (GET, POST, etc.) - %m { "method" : "xxx" } */ protected static class MethodElement implements AccessLogElement { @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { if (request != null) { result.put("method", request.getMethod()); } } } /** * write time taken to process the request - %D, %T { "elapsedMillis" : xxx } { "elapsedSeconds" : xxx } */ protected static class ElapsedTimeElement implements AccessLogElement { private final boolean millis; /** * if millis is true, write time in millis - %D * if millis is false, write time in seconds - %T */ public ElapsedTimeElement(boolean millis) { this.millis = millis; } @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { if (millis) { result.put("elapsedMillis", time); } else { // second buf.append(time / 1000); buf.append('.'); int remains = (int) (time % 1000); buf.append(remains / 100); remains = remains % 100; buf.append(remains / 10); buf.append(remains % 10); result.put("elapsedSeconds", Double.valueOf(buf.toString())); buf.delete(0, buf.length()); } } } /** * write time until first byte is written (commit time) in millis - %F { "commitTime" : xxx } */ protected static class FirstByteTimeElement implements AccessLogElement { @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { long commitTime = response.getCoyoteResponse().getCommitTime(); if (commitTime == -1) { result.put("commitTime", '-'); } else { long delta = commitTime - request.getCoyoteRequest().getStartTime(); result.put("commitTime", delta); } } } /** * write Query string (prepended with a '?' if it exists) - %q { "queryString" : "xxx" } */ protected static class QueryElement implements AccessLogElement { @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { String query = null; if (request != null) { query = request.getQueryString(); } if (query != null) { result.put("queryString", query); } } } /** * write All Parameters - %P { "params" : { "param1" : "xxx", "param2: "xxx" } }<br> * . will be replaced to $ if parameter name contains dot */ protected static class RequestParametersElement implements AccessLogElement { @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { Enumeration<String> paramNames = request.getParameterNames(); if (!paramNames.hasMoreElements()) { return; } DBObject params = new BasicDBObject(); while (paramNames.hasMoreElements()) { String paramName = paramNames.nextElement(); String replaced = paramName.replace('.', '$'); String[] values = request.getParameterValues(paramName); if(values.length == 0) { continue; } else if (values.length == 1){ params.put(replaced, values[0]); } else { params.put(replaced, values); } } result.put("params", params); } } /** * Record error message to { "error" : "xxxxx" } if error happends<br> * Code piece is copied from <code>org.apache.catalina.valves.ErrorReportValve</code> * @author qianjia * */ protected static class ErrorRecordElement implements AccessLogElement { @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { Throwable throwable = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION); try { report(buf, result, request, response, throwable); } catch (Throwable tt) { ExceptionUtils.handleThrowable(tt); } finally { buf.delete(0, buf.length()); } } /** * Prints out an error report. * * @param request The request being processed * @param response The response being generated * @param throwable The exception that occurred (which possibly wraps * a root cause exception */ protected void report(StringBuilder sb, DBObject result,Request request, Response response, Throwable throwable) { // Do nothing on non-HTTP responses int statusCode = response.getStatus(); // Do nothing on a 1xx, 2xx and 3xx status and throwable is null if (throwable == null && statusCode < 400) { return; } String message = RequestUtil.filter(response.getMessage()); if (message == null) { if (throwable != null) { String exceptionMessage = throwable.getMessage(); if (exceptionMessage != null && exceptionMessage.length() > 0) { message = RequestUtil.filter( (new Scanner(exceptionMessage)).nextLine()); } } if (message == null) { message = ""; } } // Do nothing if there is no report for the specified status code and // no error message provided String report = null; StringManager smClient = StringManager.getManager(Constants.Package, request.getLocales()); try { report = smClient.getString("http." + statusCode); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); } if (report == null) { if (message.length() == 0) { return; } else { report = smClient.getString("errorReportValve.noDescription"); } } sb.append(smClient.getString("errorReportValve.statusHeader", String.valueOf(statusCode), message)).append("\n"); sb.append("type "); if (throwable != null) { sb.append(smClient.getString("errorReportValve.exceptionReport")); } else { sb.append(smClient.getString("errorReportValve.statusReport")); } sb.append("\n"); sb.append(smClient.getString("errorReportValve.message")).append(": "); sb.append(message).append("\n"); sb.append(smClient.getString("errorReportValve.description")).append(": "); sb.append(report).append("\n"); if (throwable != null) { String stackTrace = getStackTraces(throwable); sb.append(RequestUtil.filter(stackTrace)).append("\n"); int loops = 0; Throwable rootCause = throwable.getCause(); while (rootCause != null && (loops < 10)) { stackTrace = getStackTraces(rootCause); sb.append(smClient.getString("errorReportValve.rootCause")).append("\n"); sb.append(RequestUtil.filter(stackTrace)).append("\n"); // In case root cause is somehow heavily nested rootCause = rootCause.getCause(); loops++; } } result.put("error", sb.toString()); } /** * Print out a partial servlet stack trace (truncating at the last * occurrence of javax.servlet.). */ protected String getStackTraces(Throwable t) { StringBuilder trace = new StringBuilder(); trace.append(t.toString()).append('\n'); StackTraceElement[] elements = t.getStackTrace(); for (int i = 0; i < elements.length; i++) { trace.append('\t').append(elements[i].toString()).append('\n'); } return trace.toString(); } } /** * write user session ID - %S { "sessionId" : "xxx" } */ protected static class SessionIdElement implements AccessLogElement { @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { if (request == null) { result.put("sessionId", '-'); } else { Session session = request.getSessionInternal(false); if (session == null) { result.put("sessionId", '-'); } else { result.put("sessionId", session.getIdInternal()); } } } } /** * write requested URL path - %U { "url" : "xxx" } */ protected static class RequestURIElement implements AccessLogElement { @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { if (request != null) { result.put("url", request.getRequestURI()); } else { result.put("url", '-'); } } } /** * write local server name - %v { "serverName" : "xxx" } */ protected static class LocalServerNameElement implements AccessLogElement { @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { result.put("serverName", request.getServerName()); } } /** * write any string */ protected static class NoopElement implements AccessLogElement { public static final NoopElement INSTANCE = new NoopElement(); public NoopElement() { } @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { } } /** * write incoming headers - %{xxx}i {"requestHeaders" : { "header1" : "xxx", "header2" : "xxx"} } */ protected static class HeaderElement implements AccessLogElement { private final String header; public HeaderElement(String header) { this.header = header; } @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { Enumeration<String> iter = request.getHeaders(header); if (iter.hasMoreElements()) { buf.append(iter.nextElement()); while (iter.hasMoreElements()) { buf.append(',').append(iter.nextElement()); } } else { buf.append('-'); } DBObject headers = (DBObject) result.get("requestHeaders"); if(headers == null) { headers = new BasicDBObject(); result.put("requestHeaders", headers); } headers.put(header, buf.toString()); buf.delete(0, buf.length()); } } /** * write a specific cookie - %{xxx}c {"cookies" : { "cookie1" : "xxx", "cookie2" : "xxx"} } */ protected static class CookieElement implements AccessLogElement { private final String header; public CookieElement(String header) { this.header = header; } @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { String value = "-"; Cookie[] c = request.getCookies(); if (c != null) { for (int i = 0; i < c.length; i++) { if (header.equals(c[i].getName())) { value = c[i].getValue(); break; } } } DBObject cookies = (DBObject) result.get("cookies"); if(cookies == null) { cookies = new BasicDBObject(); result.put("cookies", cookies); } cookies.put(header, value); } } /** * write a specific response header - %{xxx}o {"responseHeaders" : { "header1" : "xxx", "header2" : "xxx"} } */ protected static class ResponseHeaderElement implements AccessLogElement { private final String header; public ResponseHeaderElement(String header) { this.header = header; } @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { if (null != response) { Iterator<String> iter = response.getHeaders(header).iterator(); if (iter.hasNext()) { buf.append(iter.next()); while (iter.hasNext()) { buf.append(',').append(iter.next()); } } else { buf.append('-'); } } else { buf.append('-'); } DBObject headers = (DBObject) result.get("responseHeaders"); if(headers == null) { headers = new BasicDBObject(); result.put("responseHeaders", headers); } headers.put(header, buf.toString()); buf.delete(0, buf.length()); } } /** * write an attribute in the ServletRequest - %{xxx}r {"requestAttrs" : { "attr1" : "xxx", "attr2" : "xxx"} } */ protected static class RequestAttributeElement implements AccessLogElement { private final String header; public RequestAttributeElement(String header) { this.header = header; } @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { Object value = null; if (request != null) { value = request.getAttribute(header); } else { value = "??"; } if (value != null) { if (value instanceof String) { buf.append((String) value); } else { buf.append(value.toString()); } } else { buf.append('-'); } DBObject headers = (DBObject) result.get("requestAttrs"); if(headers == null) { headers = new BasicDBObject(); result.put("requestAttrs", headers); } headers.put(header, buf.toString()); buf.delete(0, buf.length()); } } /** * write an attribute in the HttpSession - %{xxx}s {"sessionAttrs" : { "attr1" : "xxx", "attr2" : "xxx"} } */ protected static class SessionAttributeElement implements AccessLogElement { private final String header; public SessionAttributeElement(String header) { this.header = header; } @Override public void addElement(StringBuilder buf, DBObject result, Date date, Request request, Response response, long time) { Object value = null; if (null != request) { HttpSession sess = request.getSession(false); if (null != sess) { value = sess.getAttribute(header); } } else { value = "??"; } if (value != null) { if (value instanceof String) { buf.append((String) value); } else { buf.append(value.toString()); } } else { buf.append('-'); } DBObject headers = (DBObject) result.get("sessionAttrs"); if(headers == null) { headers = new BasicDBObject(); result.put("sessionAttrs", headers); } headers.put(header, buf.toString()); buf.delete(0, buf.length()); } } /** * parse pattern string and create the array of AccessLogElement */ protected AccessLogElement[] createLogElements() { List<AccessLogElement> list = new ArrayList<AccessLogElement>(); boolean replace = false; StringBuilder buf = new StringBuilder(); for (int i = 0; i < pattern.length(); i++) { char ch = pattern.charAt(i); if (replace) { /* * For code that processes {, the behavior will be ... if I do * not encounter a closing } - then I ignore the { */ if ('{' == ch) { StringBuilder name = new StringBuilder(); int j = i + 1; for (; j < pattern.length() && '}' != pattern.charAt(j); j++) { name.append(pattern.charAt(j)); } if (j + 1 < pattern.length()) { /* the +1 was to account for } which we increment now */ j++; list.add(createAccessLogElement(name.toString(), pattern.charAt(j))); i = j; /* Since we walked more than one character */ } else { // D'oh - end of string - pretend we never did this // and do processing the "old way" list.add(createAccessLogElement(ch)); } } else { list.add(createAccessLogElement(ch)); } replace = false; } else if (ch == '%') { replace = true; list.add(NoopElement.INSTANCE); buf = new StringBuilder(); } else { buf.append(ch); } } if(this.recordError) { list.add(new ErrorRecordElement()); } if (buf.length() > 0) { list.add(new NoopElement()); } return list.toArray(new AccessLogElement[0]); } /** * create an AccessLogElement implementation which needs header string */ protected AccessLogElement createAccessLogElement(String header, char pattern) { switch (pattern) { case 'i': return new HeaderElement(header); case 'c': return new CookieElement(header); case 'o': return new ResponseHeaderElement(header); case 'r': return new RequestAttributeElement(header); case 's': return new SessionAttributeElement(header); case 't': return new DateAndTimeElement(header); default: return NoopElement.INSTANCE; } } /** * create an AccessLogElement implementation */ protected AccessLogElement createAccessLogElement(char pattern) { switch (pattern) { case 'a': return new RemoteAddrElement(); case 'A': return new LocalAddrElement(); case 'b': return new ByteSentElement(true); case 'B': return new ByteSentElement(false); case 'D': return new ElapsedTimeElement(true); case 'F': return new FirstByteTimeElement(); case 'h': return new HostElement(); case 'H': return new ProtocolElement(); case 'l': return new LogicalUserNameElement(); case 'm': return new MethodElement(); case 'p': return new LocalPortElement(); case 'q': return new QueryElement(); case 'r': return new RequestElement(); case 's': return new HttpStatusCodeElement(); case 'S': return new SessionIdElement(); case 't': return new DateAndTimeElement(); case 'T': return new ElapsedTimeElement(false); case 'u': return new UserElement(); case 'U': return new RequestURIElement(); case 'v': return new LocalServerNameElement(); case 'I': return new ThreadNameElement(); case 'P': return new RequestParametersElement(); default: return NoopElement.INSTANCE; } } public void setCapSize(int capSize) { this.capSize = capSize; } public void setCollName(String collName) { this.collName = collName; } public String getCollName() { return collName; } public void setExcludes(String excludes) { if(excludes == null) { return; } this.excludes = excludes; this.excludePatterns = excludes.split(","); } public void setRecordError(boolean recordError) { this.recordError = recordError; } public void setUri(String uri) { this.uri = uri; } public void setDbName(String dbName) { this.dbName = dbName; } }