Modified: tomcat/trunk/java/org/apache/catalina/valves/AccessLogValve.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/valves/AccessLogValve.java?rev=1557113&r1=1557112&r2=1557113&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/valves/AccessLogValve.java (original) +++ tomcat/trunk/java/org/apache/catalina/valves/AccessLogValve.java Fri Jan 10 13:09:50 2014 @@ -25,104 +25,28 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; -import java.net.InetAddress; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Date; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; import java.util.Locale; import java.util.TimeZone; -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.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.buf.B2CConverter; -import org.apache.tomcat.util.collections.SynchronizedStack; /** - * <p>Implementation of the <b>Valve</b> interface that generates a web server - * access log with the detailed line contents matching a configurable pattern. - * The syntax of the available patterns is similar to that supported by the - * <a href="http://httpd.apache.org/">Apache HTTP Server</a> - * <code>mod_log_config</code> module. As an additional feature, - * automatic rollover of log files when the date changes is also supported.</p> - * - * <p>Patterns for the logged message may include constant text or any of the - * following replacement strings, for which the corresponding information - * from the specified Response is substituted:</p> - * <ul> - * <li><b>%a</b> - Remote IP address - * <li><b>%A</b> - Local IP address - * <li><b>%b</b> - Bytes sent, excluding HTTP headers, or '-' if no bytes - * were sent - * <li><b>%B</b> - Bytes sent, excluding HTTP headers - * <li><b>%h</b> - Remote host name (or IP address if - * <code>enableLookups</code> for the connector is false) - * <li><b>%H</b> - Request protocol - * <li><b>%l</b> - Remote logical username from identd (always returns '-') - * <li><b>%m</b> - Request method - * <li><b>%p</b> - Local port - * <li><b>%q</b> - Query string (prepended with a '?' if it exists, otherwise - * an empty string - * <li><b>%r</b> - First line of the request - * <li><b>%s</b> - HTTP status code of the response - * <li><b>%S</b> - User session ID - * <li><b>%t</b> - Date and time, in Common Log Format format - * <li><b>%t{format}</b> - Date and time, in any format supported by SimpleDateFormat - * <li><b>%u</b> - Remote user that was authenticated - * <li><b>%U</b> - Requested URL path - * <li><b>%v</b> - Local server name - * <li><b>%D</b> - Time taken to process the request, in millis - * <li><b>%T</b> - Time taken to process the request, in seconds - * <li><b>%I</b> - current Request thread name (can compare later with stacktraces) - * </ul> - * <p>In addition, the caller can specify one of the following aliases for - * commonly utilized patterns:</p> - * <ul> - * <li><b>common</b> - <code>%h %l %u %t "%r" %s %b</code> - * <li><b>combined</b> - - * <code>%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"</code> - * </ul> - * - * <p> - * There is also support to write information from the cookie, incoming - * header, the Session or something else in the ServletRequest.<br> - * It is modeled after the - * <a href="http://httpd.apache.org/">Apache HTTP Server</a> log configuration - * syntax:</p> + * This is a concrete implementation of {@link AbstractAccessLogValve} that + * outputs the access log to a file. The features of this implementation + * include: * <ul> - * <li><code>%{xxx}i</code> for incoming headers - * <li><code>%{xxx}o</code> for outgoing response headers - * <li><code>%{xxx}c</code> for a specific cookie - * <li><code>%{xxx}r</code> xxx is an attribute in the ServletRequest - * <li><code>%{xxx}s</code> xxx is an attribute in the HttpSession - * <li><code>%{xxx}t</code> xxx is an enhanced SimpleDateFormat pattern - * (see Configuration Reference document for details on supported time patterns) + * <li>Automatic date-based rollover of log files</li> + * <li>Optional log file rotation</li> * </ul> - * - * <p> - * Log rotation can be on or off. This is dictated by the - * <code>rotatable</code> property. - * </p> - * * <p> * For UNIX users, another field called <code>checkExists</code> is also * available. If set to true, the log file's existence will be checked before @@ -135,40 +59,14 @@ import org.apache.tomcat.util.collection * been made available to allow you to tell this instance to move * the existing log file to somewhere else and start writing a new log file. * </p> - * - * <p> - * Conditional logging is also supported. This can be done with the - * <code>conditionUnless</code> and <code>conditionIf</code> properties. - * If the value returned from ServletRequest.getAttribute(conditionUnless) - * yields a non-null value, the logging will be skipped. - * If the value returned from ServletRequest.getAttribute(conditionIf) - * yields the null value, the logging will be skipped. - * The <code>condition</code> attribute is synonym for - * <code>conditionUnless</code> and is provided for backwards compatibility. - * </p> - * - * <p> - * For extended attributes coming from a getAttribute() call, - * it is you responsibility to ensure there are no newline or - * control characters. - * </p> - * - * @author Craig R. McClanahan - * @author Jason Brittain - * @author Remy Maucherat - * @author Takayuki Kaneko - * @author Peter Rossbach - * - * @version $Id$ */ - -public class AccessLogValve extends ValveBase implements AccessLog { +public class AccessLogValve extends AbstractAccessLogValve { private static final Log log = LogFactory.getLog(AccessLogValve.class); //------------------------------------------------------ Constructor public AccessLogValve() { - super(true); + super(); } // ----------------------------------------------------- Instance Variables @@ -186,18 +84,6 @@ public class AccessLogValve extends Valv */ private String directory = "logs"; - - /** - * enabled this component - */ - protected boolean enabled = true; - - /** - * The pattern used to format our access log lines. - */ - protected String pattern = null; - - /** * The prefix that is added to log file filenames. */ @@ -242,260 +128,12 @@ public class AccessLogValve extends Valv /** - * The size of our global date format cache - */ - private static final int globalCacheSize = 300; - - /** - * The size of our thread local date format cache - */ - private static final int localCacheSize = 60; - - - /** * The current log file we are writing to. Helpful when checkExists * is true. */ protected File currentLogFile = null; /** - * <p>Cache structure for formatted timestamps based on seconds.</p> - * - * <p>The cache consists of entries for a consecutive range of - * seconds. The length of the range is configurable. It is - * implemented based on a cyclic buffer. New entries shift the range.</p> - * - * <p>There is one cache for the CLF format (the access log standard - * format) and a HashMap of caches for additional formats used by - * SimpleDateFormat.</p> - * - * <p>Although the cache supports specifying a locale when retrieving a - * formatted timestamp, each format will always use the locale given - * when the format was first used. New locales can only be used for new formats. - * The CLF format will always be formatted using the locale - * <code>en_US</code>.</p> - * - * <p>The cache is not threadsafe. It can be used without synchronization - * via thread local instances, or with synchronization as a global cache.</p> - * - * <p>The cache can be created with a parent cache to build a cache hierarchy. - * Access to the parent cache is threadsafe.</p> - * - * <p>This class uses a small thread local first level cache and a bigger - * synchronized global second level cache.</p> - */ - protected static class DateFormatCache { - - protected class Cache { - - /* CLF log format */ - private static final String cLFFormat = "dd/MMM/yyyy:HH:mm:ss Z"; - - /* Second used to retrieve CLF format in most recent invocation */ - private long previousSeconds = Long.MIN_VALUE; - /* Value of CLF format retrieved in most recent invocation */ - private String previousFormat = ""; - - /* First second contained in cache */ - private long first = Long.MIN_VALUE; - /* Last second contained in cache */ - private long last = Long.MIN_VALUE; - /* Index of "first" in the cyclic cache */ - private int offset = 0; - /* Helper object to be able to call SimpleDateFormat.format(). */ - private final Date currentDate = new Date(); - - protected final String cache[]; - private SimpleDateFormat formatter; - private boolean isCLF = false; - - private Cache parent = null; - - private Cache(Cache parent) { - this(null, parent); - } - - private Cache(String format, Cache parent) { - this(format, null, parent); - } - - private Cache(String format, Locale loc, Cache parent) { - cache = new String[cacheSize]; - for (int i = 0; i < cacheSize; i++) { - cache[i] = null; - } - if (loc == null) { - loc = cacheDefaultLocale; - } - if (format == null) { - isCLF = true; - format = cLFFormat; - formatter = new SimpleDateFormat(format, Locale.US); - } else { - formatter = new SimpleDateFormat(format, loc); - } - formatter.setTimeZone(TimeZone.getDefault()); - this.parent = parent; - } - - private String getFormatInternal(long time) { - - long seconds = time / 1000; - - /* First step: if we have seen this timestamp - during the previous call, and we need CLF, return the previous value. */ - if (seconds == previousSeconds) { - return previousFormat; - } - - /* Second step: Try to locate in cache */ - previousSeconds = seconds; - int index = (offset + (int)(seconds - first)) % cacheSize; - if (index < 0) { - index += cacheSize; - } - if (seconds >= first && seconds <= last) { - if (cache[index] != null) { - /* Found, so remember for next call and return.*/ - previousFormat = cache[index]; - return previousFormat; - } - - /* Third step: not found in cache, adjust cache and add item */ - } else if (seconds >= last + cacheSize || seconds <= first - cacheSize) { - first = seconds; - last = first + cacheSize - 1; - index = 0; - offset = 0; - for (int i = 1; i < cacheSize; i++) { - cache[i] = null; - } - } else if (seconds > last) { - for (int i = 1; i < seconds - last; i++) { - cache[(index + cacheSize - i) % cacheSize] = null; - } - first = seconds - (cacheSize - 1); - last = seconds; - offset = (index + 1) % cacheSize; - } else if (seconds < first) { - for (int i = 1; i < first - seconds; i++) { - cache[(index + i) % cacheSize] = null; - } - first = seconds; - last = seconds + (cacheSize - 1); - offset = index; - } - - /* Last step: format new timestamp either using - * parent cache or locally. */ - if (parent != null) { - synchronized(parent) { - previousFormat = parent.getFormatInternal(time); - } - } else { - currentDate.setTime(time); - previousFormat = formatter.format(currentDate); - if (isCLF) { - StringBuilder current = new StringBuilder(32); - current.append('['); - current.append(previousFormat); - current.append(']'); - previousFormat = current.toString(); - } - } - cache[index] = previousFormat; - return previousFormat; - } - } - - /* Number of cached entries */ - private int cacheSize = 0; - - private final Locale cacheDefaultLocale; - private final DateFormatCache parent; - protected final Cache cLFCache; - private final HashMap<String, Cache> formatCache = new HashMap<>(); - - protected DateFormatCache(int size, Locale loc, DateFormatCache parent) { - cacheSize = size; - cacheDefaultLocale = loc; - this.parent = parent; - Cache parentCache = null; - if (parent != null) { - synchronized(parent) { - parentCache = parent.getCache(null, null); - } - } - cLFCache = new Cache(parentCache); - } - - private Cache getCache(String format, Locale loc) { - Cache cache; - if (format == null) { - cache = cLFCache; - } else { - cache = formatCache.get(format); - if (cache == null) { - Cache parentCache = null; - if (parent != null) { - synchronized(parent) { - parentCache = parent.getCache(format, loc); - } - } - cache = new Cache(format, loc, parentCache); - formatCache.put(format, cache); - } - } - return cache; - } - - public String getFormat(long time) { - return cLFCache.getFormatInternal(time); - } - - public String getFormat(String format, Locale loc, long time) { - return getCache(format, loc).getFormatInternal(time); - } - } - - /** - * Global date format cache. - */ - private static final DateFormatCache globalDateCache = - new DateFormatCache(globalCacheSize, Locale.getDefault(), null); - - /** - * Thread local date format cache. - */ - private static final ThreadLocal<DateFormatCache> localDateCache = - new ThreadLocal<DateFormatCache>() { - @Override - protected DateFormatCache initialValue() { - return new DateFormatCache(localCacheSize, Locale.getDefault(), globalDateCache); - } - }; - - - /** - * 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(); - } - }; - - /** - * The list of our format types. - */ - private static enum FormatType { - CLF, SEC, MSEC, MSEC_FRAC, SDF - } - - /** * Instant when the log daily rotation was last checked. */ private volatile long rotationLastChecked = 0L; @@ -506,38 +144,11 @@ public class AccessLogValve extends Valv */ private boolean checkExists = 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; - /** * Date format to place in log file name. */ protected String fileDateFormat = ".yyyy-MM-dd"; - - /** - * Name of locale used to format timestamps in log entries and in - * log file name suffix. - */ - protected String localeName = Locale.getDefault().toString(); - - - /** - * Locale used to format timestamps in log entries and in - * log file name suffix. - */ - protected Locale locale = Locale.getDefault(); - /** * Character set used by the log file. If it is <code>null</code>, the * system default character set will be used. An empty string will be @@ -545,71 +156,8 @@ public class AccessLogValve extends Valv */ protected String encoding = null; - /** - * Array of AccessLogElement, they will be used to make log message. - */ - protected AccessLogElement[] logElements = null; - - /** - * @see #setRequestAttributesEnabled(boolean) - */ - protected boolean requestAttributesEnabled = false; - - /** - * Buffer pool used for log message generation. Pool used to reduce garbage - * generation. - */ - private SynchronizedStack<CharArrayWriter> charArrayWriters = - new SynchronizedStack<>(); - - /** - * Log message buffers are usually recycled and re-used. To prevent - * excessive memory usage, if a buffer grows beyond this size it will be - * discarded. The default is 256 characters. This should be set to larger - * than the typical access log message size. - */ - private int maxLogMessageBufferSize = 256; - // ------------------------------------------------------------- Properties - public int getMaxLogMessageBufferSize() { - return maxLogMessageBufferSize; - } - - public void setMaxLogMessageBufferSize(int maxLogMessageBufferSize) { - this.maxLogMessageBufferSize = maxLogMessageBufferSize; - } - - /** - * {@inheritDoc} - */ - @Override - public void setRequestAttributesEnabled(boolean requestAttributesEnabled) { - this.requestAttributesEnabled = requestAttributesEnabled; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean getRequestAttributesEnabled() { - return requestAttributesEnabled; - } - - /** - * @return Returns the enabled. - */ - public boolean getEnabled() { - return enabled; - } - - /** - * @param enabled - * The enabled to set. - */ - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } /** * Return the directory in which we create log files. @@ -628,34 +176,6 @@ public class AccessLogValve extends Valv this.directory = directory; } - - /** - * 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 { - this.pattern = pattern; - } - logElements = createLogElements(); - } - - /** * Check for file existence before logging. */ @@ -769,68 +289,6 @@ public class AccessLogValve extends Valv this.suffix = suffix; } - - /** - * 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; - } - /** * Return the date format date based log rotation. */ @@ -857,29 +315,6 @@ public class AccessLogValve extends Valv } } - - /** - * Return the locale used to format timestamps in log entries and in - * log file name suffix. - */ - public String getLocale() { - return localeName; - } - - - /** - * Set the locale used to format timestamps in log entries and in - * log file name suffix. Changing the locale is only supported - * as long as the AccessLogValve has not logged anything. Changing - * the locale later can lead to inconsistent formatting. - * - * @param localeName The locale to use. - */ - public void setLocale(String localeName) { - this.localeName = localeName; - locale = findLocale(localeName, locale); - } - /** * Return the character set name that is used to write the log file. * @@ -919,60 +354,6 @@ public class AccessLogValve extends Valv } /** - * 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() || !getEnabled() || logElements == null - || condition != null - && null != request.getRequest().getAttribute(condition) - || conditionIf != null - && null == request.getRequest().getAttribute(conditionIf)) { - 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); - - CharArrayWriter result = charArrayWriters.pop(); - if (result == null) { - result = new CharArrayWriter(128); - } - - for (int i = 0; i < logElements.length; i++) { - logElements[i].addElement(result, date, request, response, time); - } - - log(result); - - if (result.size() <= maxLogMessageBufferSize) { - result.reset(); - charArrayWriters.push(result); - } - } - - - /** * Rotate the log file if necessary. */ public void rotate() { @@ -1130,6 +511,7 @@ public class AccessLogValve extends Valv * * @param message Message to be logged */ + @Override public void log(CharArrayWriter message) { rotate(); @@ -1207,40 +589,6 @@ public class AccessLogValve extends Valv } /** - * This method returns a Date object that is accurate to within one second. - * If a thread calls this method to get a Date and it's been less than 1 - * second since a new Date was created, this method simply gives out the - * same Date again so that the system doesn't spend time creating Date - * objects unnecessarily. - * - * @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("accessLogValve.invalidLocale", name)); - return fallback; - } - - - /** * Start this component and implement the requirements * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}. * @@ -1260,7 +608,7 @@ public class AccessLogValve extends Valv } open(); - setState(LifecycleState.STARTING); + super.startInternal(); } @@ -1274,848 +622,7 @@ public class AccessLogValve extends Valv @Override protected synchronized void stopInternal() throws LifecycleException { - setState(LifecycleState.STOPPING); + super.stopInternal(); close(false); } - - /** - * AccessLogElement writes the partial message into the buffer. - */ - protected interface AccessLogElement { - public void addElement(CharArrayWriter buf, Date date, Request request, - Response response, long time); - - } - - /** - * write thread name - %I - */ - protected static class ThreadNameElement implements AccessLogElement { - @Override - public void addElement(CharArrayWriter buf, Date date, Request request, - Response response, long time) { - RequestInfo info = request.getCoyoteRequest().getRequestProcessor(); - if(info != null) { - buf.append(info.getWorkerThreadName()); - } else { - buf.append("-"); - } - } - } - - /** - * write local IP address - %A - */ - 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(CharArrayWriter buf, Date date, Request request, - Response response, long time) { - buf.append(LOCAL_ADDR_VALUE); - } - } - - /** - * write remote IP address - %a - */ - protected class RemoteAddrElement implements AccessLogElement { - @Override - public void addElement(CharArrayWriter buf, Date date, Request request, - Response response, long time) { - if (requestAttributesEnabled) { - Object addr = request.getAttribute(REMOTE_ADDR_ATTRIBUTE); - if (addr == null) { - buf.append(request.getRemoteAddr()); - } else { - buf.append(addr.toString()); - } - } else { - buf.append(request.getRemoteAddr()); - } - } - } - - /** - * write remote host name - %h - */ - protected class HostElement implements AccessLogElement { - @Override - public void addElement(CharArrayWriter buf, 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 = "-"; - } - buf.append(value); - } - } - - /** - * write remote logical username from identd (always returns '-') - %l - */ - protected static class LogicalUserNameElement implements AccessLogElement { - @Override - public void addElement(CharArrayWriter buf, Date date, Request request, - Response response, long time) { - buf.append('-'); - } - } - - /** - * write request protocol - %H - */ - protected class ProtocolElement implements AccessLogElement { - @Override - public void addElement(CharArrayWriter buf, Date date, Request request, - Response response, long time) { - if (requestAttributesEnabled) { - Object proto = request.getAttribute(PROTOCOL_ATTRIBUTE); - if (proto == null) { - buf.append(request.getProtocol()); - } else { - buf.append(proto.toString()); - } - } else { - buf.append(request.getProtocol()); - } - } - } - - /** - * write remote user that was authenticated (if any), else '-' - %u - */ - protected static class UserElement implements AccessLogElement { - @Override - public void addElement(CharArrayWriter buf, Date date, Request request, - Response response, long time) { - if (request != null) { - String value = request.getRemoteUser(); - if (value != null) { - buf.append(value); - } else { - buf.append('-'); - } - } else { - buf.append('-'); - } - } - } - - /** - * write date and time, in configurable format (default CLF) - %t or %t{format} - */ - protected class DateAndTimeElement implements AccessLogElement { - - /** - * Format prefix specifying request start time - */ - private static final String requestStartPrefix = "begin"; - - /** - * Format prefix specifying response end time - */ - private static final String responseEndPrefix = "end"; - - /** - * Separator between optional prefix and rest of format - */ - private static final String prefixSeparator = ":"; - - /** - * Special format for seconds since epoch - */ - private static final String secFormat = "sec"; - - /** - * Special format for milliseconds since epoch - */ - private static final String msecFormat = "msec"; - - /** - * Special format for millisecond part of timestamp - */ - private static final String msecFractionFormat = "msec_frac"; - - /** - * The patterns we use to replace "S" and "SSS" millisecond - * formatting of SimpleDateFormat by our own handling - */ - private static final String msecPattern = "{#}"; - private static final String trippleMsecPattern = - msecPattern + msecPattern + msecPattern; - - /* Our format description string, null if CLF */ - private final String format; - /* Whether to use begin of request or end of response as the timestamp */ - private final boolean usesBegin; - /* The format type */ - private final FormatType type; - /* Whether we need to postprocess by adding milliseconds */ - private boolean usesMsecs = false; - - protected DateAndTimeElement() { - this(null); - } - - /** - * Replace the millisecond formatting character 'S' by - * some dummy characters in order to make the resulting - * formatted time stamps cacheable. We replace the dummy - * chars later with the actual milliseconds because that's - * relatively cheap. - */ - private String tidyFormat(String format) { - boolean escape = false; - StringBuilder result = new StringBuilder(); - int len = format.length(); - char x; - for (int i = 0; i < len; i++) { - x = format.charAt(i); - if (escape || x != 'S') { - result.append(x); - } else { - result.append(msecPattern); - usesMsecs = true; - } - if (x == '\'') { - escape = !escape; - } - } - return result.toString(); - } - - protected DateAndTimeElement(String header) { - String format = header; - boolean usesBegin = false; - FormatType type = FormatType.CLF; - - if (format != null) { - if (format.equals(requestStartPrefix)) { - usesBegin = true; - format = ""; - } else if (format.startsWith(requestStartPrefix + prefixSeparator)) { - usesBegin = true; - format = format.substring(6); - } else if (format.equals(responseEndPrefix)) { - usesBegin = false; - format = ""; - } else if (format.startsWith(responseEndPrefix + prefixSeparator)) { - usesBegin = false; - format = format.substring(4); - } - if (format.length() == 0) { - type = FormatType.CLF; - } else if (format.equals(secFormat)) { - type = FormatType.SEC; - } else if (format.equals(msecFormat)) { - type = FormatType.MSEC; - } else if (format.equals(msecFractionFormat)) { - type = FormatType.MSEC_FRAC; - } else { - type = FormatType.SDF; - format = tidyFormat(format); - } - } - this.format = format; - this.usesBegin = usesBegin; - this.type = type; - } - - @Override - public void addElement(CharArrayWriter buf, Date date, Request request, - Response response, long time) { - long timestamp = date.getTime(); - long frac; - if (usesBegin) { - timestamp -= time; - } - switch (type) { - case CLF: - buf.append(localDateCache.get().getFormat(timestamp)); - break; - case SEC: - buf.append(Long.toString(timestamp / 1000)); - break; - case MSEC: - buf.append(Long.toString(timestamp)); - break; - case MSEC_FRAC: - frac = timestamp % 1000; - if (frac < 100) { - if (frac < 10) { - buf.append('0'); - buf.append('0'); - } else { - buf.append('0'); - } - } - buf.append(Long.toString(frac)); - break; - case SDF: - String temp = localDateCache.get().getFormat(format, locale, timestamp); - if (usesMsecs) { - frac = timestamp % 1000; - StringBuilder trippleMsec = new StringBuilder(4); - if (frac < 100) { - if (frac < 10) { - trippleMsec.append('0'); - trippleMsec.append('0'); - } else { - trippleMsec.append('0'); - } - } - trippleMsec.append(frac); - temp = temp.replace(trippleMsecPattern, trippleMsec); - temp = temp.replace(msecPattern, Long.toString(frac)); - } - buf.append(temp); - break; - } - } - } - - /** - * write first line of the request (method and request URI) - %r - */ - protected static class RequestElement implements AccessLogElement { - @Override - public void addElement(CharArrayWriter buf, 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('-'); - } - } - } - - /** - * write HTTP status code of the response - %s - */ - protected static class HttpStatusCodeElement implements AccessLogElement { - @Override - public void addElement(CharArrayWriter buf, Date date, Request request, - Response response, long time) { - if (response != null) { - // This approach is used to reduce GC from toString conversion - int status = response.getStatus(); - if (100 <= status && status < 1000) { - buf.append((char) ('0' + (status / 100))) - .append((char) ('0' + ((status / 10) % 10))) - .append((char) ('0' + (status % 10))); - } else { - buf.append(Integer.toString(status)); - } - } else { - buf.append('-'); - } - } - } - - /** - * write local port on which this request was received - %p - */ - protected class LocalPortElement implements AccessLogElement { - @Override - public void addElement(CharArrayWriter buf, Date date, Request request, - Response response, long time) { - if (requestAttributesEnabled) { - Object port = request.getAttribute(SERVER_PORT_ATTRIBUTE); - if (port == null) { - buf.append(Integer.toString(request.getServerPort())); - } else { - buf.append(port.toString()); - } - } else { - buf.append(Integer.toString(request.getServerPort())); - } - } - } - - /** - * write bytes sent, excluding HTTP headers - %b, %B - */ - 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(CharArrayWriter buf, 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) { - buf.append('-'); - } else { - buf.append(Long.toString(length)); - } - } - } - - /** - * write request method (GET, POST, etc.) - %m - */ - protected static class MethodElement implements AccessLogElement { - @Override - public void addElement(CharArrayWriter buf, Date date, Request request, - Response response, long time) { - if (request != null) { - buf.append(request.getMethod()); - } - } - } - - /** - * write time taken to process the request - %D, %T - */ - 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(CharArrayWriter buf, Date date, Request request, - Response response, long time) { - if (millis) { - buf.append(Long.toString(time)); - } else { - // second - buf.append(Long.toString(time / 1000)); - buf.append('.'); - int remains = (int) (time % 1000); - buf.append(Long.toString(remains / 100)); - remains = remains % 100; - buf.append(Long.toString(remains / 10)); - buf.append(Long.toString(remains % 10)); - } - } - } - - /** - * write time until first byte is written (commit time) in millis - %F - */ - protected static class FirstByteTimeElement implements AccessLogElement { - @Override - public void addElement(CharArrayWriter buf, Date date, Request request, Response response, long time) { - long commitTime = response.getCoyoteResponse().getCommitTime(); - if (commitTime == -1) { - buf.append('-'); - } else { - long delta = commitTime - request.getCoyoteRequest().getStartTime(); - buf.append(Long.toString(delta)); - } - } - } - - /** - * write Query string (prepended with a '?' if it exists) - %q - */ - protected static class QueryElement implements AccessLogElement { - @Override - public void addElement(CharArrayWriter buf, Date date, Request request, - Response response, long time) { - String query = null; - if (request != null) { - query = request.getQueryString(); - } - if (query != null) { - buf.append('?'); - buf.append(query); - } - } - } - - /** - * write user session ID - %S - */ - protected static class SessionIdElement implements AccessLogElement { - @Override - public void addElement(CharArrayWriter buf, Date date, Request request, - Response response, long time) { - if (request == null) { - buf.append('-'); - } else { - Session session = request.getSessionInternal(false); - if (session == null) { - buf.append('-'); - } else { - buf.append(session.getIdInternal()); - } - } - } - } - - /** - * write requested URL path - %U - */ - protected static class RequestURIElement implements AccessLogElement { - @Override - public void addElement(CharArrayWriter buf, Date date, Request request, - Response response, long time) { - if (request != null) { - buf.append(request.getRequestURI()); - } else { - buf.append('-'); - } - } - } - - /** - * write local server name - %v - */ - protected static class LocalServerNameElement implements AccessLogElement { - @Override - public void addElement(CharArrayWriter buf, Date date, Request request, - Response response, long time) { - buf.append(request.getServerName()); - } - } - - /** - * write any string - */ - protected static class StringElement implements AccessLogElement { - private final String str; - - public StringElement(String str) { - this.str = str; - } - - @Override - public void addElement(CharArrayWriter buf, Date date, Request request, - Response response, long time) { - buf.append(str); - } - } - - /** - * write incoming headers - %{xxx}i - */ - protected static class HeaderElement implements AccessLogElement { - private final String header; - - public HeaderElement(String header) { - this.header = header; - } - - @Override - public void addElement(CharArrayWriter buf, 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()); - } - return; - } - buf.append('-'); - } - } - - /** - * write a specific cookie - %{xxx}c - */ - protected static class CookieElement implements AccessLogElement { - private final String header; - - public CookieElement(String header) { - this.header = header; - } - - @Override - public void addElement(CharArrayWriter buf, 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; - } - } - } - buf.append(value); - } - } - - /** - * write a specific response header - %{xxx}o - */ - protected static class ResponseHeaderElement implements AccessLogElement { - private final String header; - - public ResponseHeaderElement(String header) { - this.header = header; - } - - @Override - public void addElement(CharArrayWriter buf, 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()); - } - return; - } - } - buf.append('-'); - } - } - - /** - * write an attribute in the ServletRequest - %{xxx}r - */ - protected static class RequestAttributeElement implements AccessLogElement { - private final String header; - - public RequestAttributeElement(String header) { - this.header = header; - } - - @Override - public void addElement(CharArrayWriter buf, 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('-'); - } - } - } - - /** - * write an attribute in the HttpSession - %{xxx}s - */ - protected static class SessionAttributeElement implements AccessLogElement { - private final String header; - - public SessionAttributeElement(String header) { - this.header = header; - } - - @Override - public void addElement(CharArrayWriter buf, 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('-'); - } - } - } - - - /** - * parse pattern string and create the array of AccessLogElement - */ - protected AccessLogElement[] createLogElements() { - List<AccessLogElement> list = new ArrayList<>(); - 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(new StringElement(buf.toString())); - buf = new StringBuilder(); - } else { - buf.append(ch); - } - } - if (buf.length() > 0) { - list.add(new StringElement(buf.toString())); - } - 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 new StringElement("???"); - } - } - - /** - * 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(); - default: - return new StringElement("???" + pattern + "???"); - } - } }
--------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org