Index: test/org/apache/catalina/valves/Benchmarks.java =================================================================== --- test/org/apache/catalina/valves/Benchmarks.java (revision 964269) +++ test/org/apache/catalina/valves/Benchmarks.java (working copy) @@ -174,7 +174,8 @@ new TimeDateElementBenchmarkTest_Sync(), new TimeDateElementBenchmarkTest_Local(), new TimeDateElementBenchmarkTest_LocalStruct(), - new TimeDateElementBenchmarkTest_LocalStruct_SBuilder() }; + new TimeDateElementBenchmarkTest_LocalStruct_SBuilder(), + new TimeDateElementBenchmarkTest_LocalStruct_SimpleDateFormat()}; benchmark.doTest(5, tests); } @@ -192,7 +193,59 @@ return (months[index]); } } + + private static class TimeDateElementBenchmarkTest_LocalStruct_SimpleDateFormat extends + TimeDateElementBenchmarkTestBase implements Runnable { + + + @Override + public String toString() { + return "single ThreadLocal SimpleDateFormat"; + } + + private static class Struct { + public String currentDateString; + public Date currentDate = new Date(); + private SimpleDateFormat sdf = new SimpleDateFormat("[dd/MMM/yyyy:hh:mm:ss"); + } + + private ThreadLocal structLocal = new ThreadLocal() { + @Override + protected Struct initialValue() { + return new Struct(); + } + }; + + public void run() { + printDate(); + } + + public String printDate() { + getDateLocal(); + Struct struct = structLocal.get(); + if (struct.currentDateString == null) { + struct.currentDateString = getOutput(struct.sdf, struct.currentDate); + } + return struct.currentDateString; + } + + private Date getDateLocal() { + Struct struct = structLocal.get(); + long systime = System.currentTimeMillis(); + if ((systime - struct.currentDate.getTime()) > 1000) { + struct.currentDate.setTime(systime); + struct.currentDateString = null; + } + return struct.currentDate; + } + + private String getOutput(SimpleDateFormat sdf, Date date) { + return sdf.format(date); + } + + } + private static class TimeDateElementBenchmarkTest_Sync extends TimeDateElementBenchmarkTestBase implements Runnable { Index: java/org/apache/catalina/valves/AccessLogValve.java =================================================================== --- java/org/apache/catalina/valves/AccessLogValve.java (revision 964269) +++ java/org/apache/catalina/valves/AccessLogValve.java (working copy) @@ -42,10 +42,10 @@ import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.apache.catalina.util.LifecycleBase; -import org.apache.tomcat.util.res.StringManager; import org.apache.coyote.RequestInfo; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; +import org.apache.tomcat.util.res.StringManager; /** @@ -75,6 +75,8 @@ *
  • %s - HTTP status code of the response *
  • %S - User session ID *
  • %t - Date and time, in Common Log Format format + *
  • %{xxx}t - Date and time, in user format (see {@link SimpleDateFormat}). + * Warning: performance may be impacted if milliseconds are used in pattern. *
  • %u - Remote user that was authenticated *
  • %U - Requested URL path *
  • %v - Local server name @@ -152,14 +154,6 @@ /** - * The set of month abbreviations for log messages. - */ - protected static final String months[] = - { "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - - - /** * enabled this component */ protected boolean enabled = true; @@ -215,55 +209,64 @@ /** - * The system timezone. + * The current log file we are writing to. Helpful when checkExists + * is true. */ - private TimeZone timezone = null; - + protected File currentLogFile = null; + /** - * The time zone offset relative to GMT in text form when daylight saving - * is not in operation. + * Default Date and time format pattern (Common Log Format format). */ - private String timeZoneNoDST = null; + private static final String defaultDateTimeFormat = "[dd/MMM/yyyy:HH:mm:ss Z]"; - /** - * The time zone offset relative to GMT in text form when daylight saving - * is in operation. + * Current Date and time Format format for %t. Default value: Common Log + * Format format. */ - private String timeZoneDST = null; - - - /** - * The current log file we are writing to. Helpful when checkExists - * is true. - */ - protected File currentLogFile = null; + private String dateTimeFormat = defaultDateTimeFormat; + private static class AccessDateStruct { private Date currentDate = new Date(); - private String currentDateString = null; - private SimpleDateFormat dayFormatter = new SimpleDateFormat("dd"); - private SimpleDateFormat monthFormatter = new SimpleDateFormat("MM"); - private SimpleDateFormat yearFormatter = new SimpleDateFormat("yyyy"); - private SimpleDateFormat timeFormatter = new SimpleDateFormat("HH:mm:ss"); - public AccessDateStruct() { - TimeZone tz = TimeZone.getDefault(); - dayFormatter.setTimeZone(tz); - monthFormatter.setTimeZone(tz); - yearFormatter.setTimeZone(tz); - timeFormatter.setTimeZone(tz); + private String currentDateString; + private SimpleDateFormat dateTimeFormatter; + + + /** + * Should we chunk %t output to thousand millis? Default is true (like + * old behavior). Switching to false could impact performance. + */ + private boolean thousandMillisDateTimeChunk = true; + + + public AccessDateStruct(String dateFormat) { + try { + dateTimeFormatter = new SimpleDateFormat(dateFormat); + dateTimeFormatter.setTimeZone(TimeZone.getDefault()); + thousandMillisDateTimeChunk = isPatternContainsMillis(dateFormat); + currentDate = new Date(); + currentDateString = null; + } catch (IllegalArgumentException ex) { + log.warn("invalid Date and time format for %t [" + dateFormat + + "]. Using Common Log Format format."); + } } + + private boolean isPatternContainsMillis(String format) { + return format.matches(".*S+.*"); + } + } /** * The system time when we last updated the Date that this valve * uses for log lines. */ - private static final ThreadLocal currentDateStruct = + private final ThreadLocal currentDateStruct = new ThreadLocal() { @Override protected AccessDateStruct initialValue() { - return new AccessDateStruct(); + return new AccessDateStruct(dateTimeFormat); } }; /** @@ -698,26 +701,9 @@ } } + - /** - * Return the month abbreviation for the specified month, which must - * be a two-digit String. - * - * @param month Month number ("01" .. "12"). - */ - private String lookup(String month) { - int index; - try { - index = Integer.parseInt(month) - 1; - } catch (Throwable t) { - index = 0; // Can not happen, in theory - } - return (months[index]); - } - - - /** * Open the new log file for the date specified by dateStamp. */ protected synchronized void open() { @@ -759,49 +745,24 @@ */ private Date getDate() { // Only create a new Date once per second, max. - long systime = System.currentTimeMillis(); + long systime = getSysTime(); AccessDateStruct struct = currentDateStruct.get(); - if ((systime - struct.currentDate.getTime()) > 1000) { + if (struct.thousandMillisDateTimeChunk + || (systime - struct.currentDate.getTime()) > 1000) { struct.currentDate.setTime(systime); struct.currentDateString = null; } return struct.currentDate; } - - - private String getTimeZone(Date date) { - if (timezone.inDaylightTime(date)) { - return timeZoneDST; - } else { - return timeZoneNoDST; - } - } - - private String calculateTimeZoneOffset(long offset) { - StringBuilder tz = new StringBuilder(); - if ((offset < 0)) { - tz.append("-"); - offset = -offset; - } else { - tz.append("+"); - } - - long hourOffset = offset / (1000 * 60 * 60); - long minuteOffset = (offset / (1000 * 60)) % 60; - - if (hourOffset < 10) - tz.append("0"); - tz.append(hourOffset); - - if (minuteOffset < 10) - tz.append("0"); - tz.append(minuteOffset); - - return tz.toString(); + /** + * Current system time. Created as the hook for date and time-sensitive tests. + * @return current time in millis. + */ + protected long getSysTime() { + return System.currentTimeMillis(); } - /** * Start this component and implement the requirements * of {@link LifecycleBase#startInternal()}. @@ -813,15 +774,11 @@ protected synchronized void startInternal() throws LifecycleException { // Initialize the timeZone, Date formatters, and currentDate - timezone = TimeZone.getDefault(); - timeZoneNoDST = calculateTimeZoneOffset(timezone.getRawOffset()); - int offset = timezone.getDSTSavings(); - timeZoneDST = calculateTimeZoneOffset(timezone.getRawOffset() + offset); - if (fileDateFormat == null || fileDateFormat.length() == 0) fileDateFormat = "yyyy-MM-dd"; + fileDateFormatter = new SimpleDateFormat(fileDateFormat); - fileDateFormatter.setTimeZone(timezone); + fileDateFormatter.setTimeZone(TimeZone.getDefault()); dateStamp = fileDateFormatter.format(currentDateStruct.get().currentDate); open(); @@ -951,32 +908,37 @@ /** * write date and time, in Common Log Format - %t + * write date and time, in specified log pattern - %{xxx}t (see {@link SimpleDateFormat}) */ protected class DateAndTimeElement implements AccessLogElement { + /** + * Constructs old-style %t date and time format in Common Log Format + */ + public DateAndTimeElement() { + // Old-style %t + } - - + /** + * Constructs custom %{TIME_FORMAT}t (see {@link SimpleDateFormat} for + * format information).
    + * Warning: performance may be impacted if milliseconds are used in + * pattern. + * @param format user format which should be used to display date and time. + */ + public DateAndTimeElement(String format) { + dateTimeFormat = format; + } + public void addElement(StringBuilder buf, Date date, Request request, Response response, long time) { AccessDateStruct struct = currentDateStruct.get(); if (struct.currentDateString == null) { - StringBuilder current = new StringBuilder(32); - current.append('['); - current.append(struct.dayFormatter.format(date)); - current.append('/'); - current.append(lookup(struct.monthFormatter.format(date))); - current.append('/'); - current.append(struct.yearFormatter.format(date)); - current.append(':'); - current.append(struct.timeFormatter.format(date)); - current.append(' '); - current.append(getTimeZone(date)); - current.append(']'); - struct.currentDateString = current.toString(); + struct.currentDateString = struct.dateTimeFormatter.format(date); } buf.append(struct.currentDateString); } + } /** @@ -1370,6 +1332,8 @@ return new RequestAttributeElement(header); case 's': return new SessionAttributeElement(header); + case 't': + return new DateAndTimeElement(header); default: return new StringElement("???"); }