You are viewing a plain text version of this content. The canonical link for it is here.
Posted to log4j-dev@logging.apache.org by ca...@apache.org on 2004/12/24 09:28:29 UTC
cvs commit: logging-log4j/src/java/org/apache/log4j/pattern CachedDateFormat.java DatePatternConverter.java CacheUtil.java
carnold 2004/12/24 00:28:29
Modified: src/java/org/apache/log4j/pattern CachedDateFormat.java
DatePatternConverter.java
Removed: src/java/org/apache/log4j/pattern CacheUtil.java
Log:
Bug 32064: CachedDateFormat rework
Revision Changes Path
1.13 +235 -88 logging-log4j/src/java/org/apache/log4j/pattern/CachedDateFormat.java
Index: CachedDateFormat.java
===================================================================
RCS file: /home/cvs/logging-log4j/src/java/org/apache/log4j/pattern/CachedDateFormat.java,v
retrieving revision 1.12
retrieving revision 1.13
diff -u -r1.12 -r1.13
--- CachedDateFormat.java 23 Dec 2004 22:50:48 -0000 1.12
+++ CachedDateFormat.java 24 Dec 2004 08:28:28 -0000 1.13
@@ -27,48 +27,132 @@
/**
- * Caches the results of a DateFormat.
+ * CachedDateFormat optimizes the performance of a wrapped
+ * DateFormat. The implementation is not thread-safe.
+ * If the millisecond pattern is not recognized,
+ * the class will only use the cache if the
+ * same value is requested.
+ *
* @author Curt Arnold
* @since 1.3
*/
final class CachedDateFormat extends DateFormat {
- private static final int BAD_PATTERN = -1;
- private static final int NO_MILLISECONDS = -2;
+ /*
+ * Constant used to represent that there was no change
+ * observed when changing the millisecond count.
+ */
+ public static final int NO_MILLISECONDS = -2;
+
+ /**
+ * Supported digit set. If the wrapped DateFormat uses
+ * a different unit set, the millisecond pattern
+ * will not be recognized and duplicate requests
+ * will use the cache.
+ */
+ private final static String digits = "0123456789";
+
+ /*
+ * Constant used to represent that there was an
+ * observed change, but was an expected change.
+ */
+ public static final int UNRECOGNIZED_MILLISECONDS = -1;
+
+ /**
+ * First magic number used to detect the millisecond position.
+ */
+ private static final int magic1 = 654;
+
+ /**
+ * Expected representation of first magic number.
+ */
+ private static final String magicString1 = "654";
+
+ /**
+ * Second magic number used to detect the millisecond position.
+ */
+ private static final int magic2 = 987;
+
+ /**
+ * Expected representation of second magic number.
+ */
+ private static final String magicString2 = "987";
+
+
+ /**
+ * Expected representation of 0 milliseconds.
+ */
+ private static final String zeroString = "000";
- // Given that the JVM precision is 1/1000 of a second, 3 digit millisecond
- // precision is the best we can ever expect.
- private static final int JVM_MAX_MILLI_DIGITS = 3;
+ /**
+ * Wrapped formatter.
+ */
+ private final DateFormat formatter;
- private DateFormat formatter;
+ /**
+ * Index of initial digit of millisecond pattern or
+ * UNRECOGNIZED_MILLISECONDS or NO_MILLISECONDS.
+ */
private int millisecondStart;
- private StringBuffer cache = new StringBuffer();
+
+ /**
+ * Cache of previous conversion.
+ */
+ private StringBuffer cache = new StringBuffer(50);
+
+ /**
+ * Integral second preceding the previous convered Date.
+ */
private long slotBegin;
- private Date slotBeginDate;
- private int milliDigits;
- private StringBuffer milliBuf = new StringBuffer(JVM_MAX_MILLI_DIGITS);
- private NumberFormat numberFormat;
+ /**
+ * Maximum validity period for the cache.
+ * Typically 1, use cache for duplicate requests only, or
+ * 1000, use cache for requests within the same integral second.
+ */
+ private final int expiration;
+
+ /**
+ * Date requested in previous conversion.
+ */
+ private long previousTime;
+
+ /**
+ * Scratch date object used to minimize date object creation.
+ */
+ private final Date tmpDate = new Date(0);
- public CachedDateFormat(DateFormat dateFormat) {
+ /**
+ * Creates a new CachedDateFormat object.
+ * @param dateFormat Date format, may not be null.
+ * @param expiration maximum cached range in milliseconds.
+ * If the dateFormat is known to be incompatible with the
+ * caching algorithm, use a value of 0 to totally disable
+ * caching or 1 to only use cache for duplicate requests.
+ */
+ public CachedDateFormat(final DateFormat dateFormat,
+ final int expiration) {
if (dateFormat == null) {
throw new IllegalArgumentException("dateFormat cannot be null");
}
formatter = dateFormat;
-
- numberFormat = new DecimalFormat();
- // numberFormat will zero as necessary
- numberFormat.setMinimumIntegerDigits(JVM_MAX_MILLI_DIGITS);
+ this.expiration = expiration;
Date now = new Date();
long nowTime = now.getTime();
slotBegin = (nowTime / 1000L) * 1000L;
+ //
+ // if now is before 1970 and slot begin
+ // was truncated forward
+ //
+ if (slotBegin > nowTime) {
+ slotBegin -= 1000;
+ }
- slotBeginDate = new Date(slotBegin);
- String formatted = formatter.format(slotBeginDate);
+ String formatted = formatter.format(now);
cache.append(formatted);
- millisecondStart = findMillisecondStart(slotBegin, formatted, formatter);
- //System.out.println("millisecondStart="+millisecondStart);
+ previousTime = nowTime;
+ millisecondStart = findMillisecondStart(nowTime, formatted, formatter);
}
@@ -82,88 +166,150 @@
* -1 indicates no millisecond field, -2 indicates unrecognized
* field (likely RelativeTimeDateFormat)
*/
- private int findMillisecondStart(
+ public static int findMillisecondStart(
final long time, final String formatted, final DateFormat formatter) {
- String plus987 = formatter.format(new Date(time + 987));
-
- //System.out.println("-formatted="+formatted);
- //System.out.println("plus987="+plus987);
- // find first difference between values
- for (int i = 0; i < formatted.length(); i++) {
- if (formatted.charAt(i) != plus987.charAt(i)) {
- // if one string has "000" and the other "987"
- // we have found the millisecond field
- if (i + 3 <= formatted.length()
- && "000".equals(formatted.substring(i, i + JVM_MAX_MILLI_DIGITS))
- && "987".equals(plus987.substring(i, i + JVM_MAX_MILLI_DIGITS))) {
- return i;
- } else {
- return BAD_PATTERN;
- }
- }
+
+ long slotBegin = (time / 1000) * 1000;
+ if (slotBegin > time) {
+ slotBegin -= 1000;
+ }
+ int millis = (int) (time - slotBegin);
+
+ int magic = magic1;
+ String magicString = magicString1;
+ if (millis == magic1) {
+ magic = magic2;
+ magicString = magicString2;
+ }
+ String plusMagic = formatter.format(new Date(slotBegin + magic));
+
+ /**
+ * If the string lengths differ then
+ * we can't use the cache except for duplicate requests.
+ */
+ if (plusMagic.length() != formatted.length()) {
+ return UNRECOGNIZED_MILLISECONDS;
+ } else {
+ // find first difference between values
+ for (int i = 0; i < formatted.length(); i++) {
+ if (formatted.charAt(i) != plusMagic.charAt(i)) {
+ //
+ // determine the expected digits for the base time
+ StringBuffer formattedMillis = new StringBuffer("ABC");
+ millisecondFormat(millis, formattedMillis, 0);
+
+ String plusZero = formatter.format(new Date(slotBegin));
+
+ // If the next 3 characters match the magic
+ // string and the expected string
+ if (plusZero.length() == formatted.length()
+ && magicString.regionMatches(0, plusMagic, i, magicString.length())
+ && formattedMillis.toString().regionMatches(0, formatted, i, magicString.length())
+ && zeroString.regionMatches(0, plusZero, i, zeroString.length())) {
+ return i;
+ } else {
+ return UNRECOGNIZED_MILLISECONDS;
+ }
+ }
+ }
}
return NO_MILLISECONDS;
}
/**
- * Converts a Date utilizing a previously converted
- * value if possible.
-
- @param date the date to format
- @param sbuf the string buffer to write to
- @param fieldPosition remains untouched
+ * Formats a Date into a date/time string.
+ *
+ * @param date the date to format
+ * @param sbuf the string buffer to write to
+ * @param fieldPosition remains untouched
*/
public StringBuffer format(
- Date date, StringBuffer sbuf, FieldPosition fieldPosition) {
-
- if (millisecondStart == BAD_PATTERN) {
- return formatter.format(date, sbuf, fieldPosition);
+ Date date, StringBuffer sbuf, FieldPosition fieldPosition){
+ format(date.getTime(), sbuf);
+ return sbuf;
+ }
+
+ /**
+ * Formats a millisecond count into a date/time string.
+ *
+ * @param now Number of milliseconds after midnight 1 Jan 1970 GMT.
+ * @param sbuf the string buffer to write to
+ */
+ public StringBuffer format(long now,
+ StringBuffer buf ){
+
+ //
+ // If an identical request, append the buffer and return.
+ //
+ if (now == previousTime) {
+ buf.append(cache);
+ return buf;
}
- long now = date.getTime();
- if ((now < (slotBegin + 1000L)) && (now >= slotBegin)) {
- //System.out.println("Using cached val:"+date);
-
- // If there are NO_MILLISECONDS we don't bother computing the millisecs.
- if (millisecondStart >= 0) {
- int millis = (int) (now - slotBegin);
- int cacheLength = cache.length();
-
- milliBuf.setLength(0);
- numberFormat.format(millis, milliBuf, fieldPosition);
- //System.out.println("milliBuf="+milliBuf);
- for(int j = 0; j < JVM_MAX_MILLI_DIGITS; j++) {
- cache.setCharAt(millisecondStart+j, milliBuf.charAt(j));
- }
- }
- } else {
- //System.out.println("Refreshing the cache: "+date+","+(date.getTime()%1000));
- slotBegin = (now / 1000L) * 1000L;
- int prevLength = cache.length();
- cache.setLength(0);
- formatter.format(date, cache, fieldPosition);
-
- // if the length changed then
- // recalculate the millisecond position
- if (cache.length() != prevLength && (milliDigits > 0)) {
- //System.out.println("Recomputing cached len changed oldLen="+prevLength
- // +", newLen="+cache.length());
- //
- // format the previous integral second
- StringBuffer tempBuffer = new StringBuffer(cache.length());
- slotBeginDate.setTime(slotBegin);
- formatter.format(slotBeginDate, tempBuffer, fieldPosition);
+ //
+ // If not a recognized millisecond pattern,
+ // use the wrapped formatter to update the cache
+ // and append the cache to the buffer.
+ //
+ if (millisecondStart != UNRECOGNIZED_MILLISECONDS) {
+
//
- // detect the start of the millisecond field
- millisecondStart = findMillisecondStart(slotBegin,
- tempBuffer.toString(),
- formatter);
- }
+ // If the requested time is within the same integral second
+ // as the last request and a shorter expiration was not requested.
+ if (now < slotBegin + expiration
+ && now >= slotBegin
+ && now < slotBegin + 1000L) {
+
+ //
+ // if there was a millisecond field then update it
+ //
+ if (millisecondStart >= 0 ) {
+ millisecondFormat((int) (now - slotBegin), cache, millisecondStart);
+ }
+ previousTime = now;
+ buf.append(cache);
+ return buf;
+ }
+ }
+
+
+ //
+ // could not use previous value.
+ // Call underlying formatter to format date.
+ cache.setLength(0);
+ tmpDate.setTime(now);
+ cache.append(formatter.format(tmpDate));
+ buf.append(cache);
+ previousTime = now;
+ //
+ // find the start of the millisecond field again
+ // if we had previously been able to find it.
+ if (millisecondStart != UNRECOGNIZED_MILLISECONDS) {
+ millisecondStart = findMillisecondStart(now, cache.toString(), formatter);
}
- return sbuf.append(cache);
+ return buf;
}
/**
+ * Formats a count of milliseconds (0-999) into a numeric representation.
+ * @param millis Millisecond coun between 0 and 999.
+ * @buf String buffer, may not be null.
+ * @offset Starting position in buffer, the length of the
+ * buffer must be at least offset + 3.
+ */
+ private static void millisecondFormat(final int millis,
+ final StringBuffer buf,
+ final int offset) {
+ buf.setCharAt(offset,
+ digits.charAt( millis / 100));
+ buf.setCharAt(offset + 1,
+ digits.charAt((millis / 10) % 10));
+ buf.setCharAt(offset + 2,
+ digits.charAt(millis % 10));
+ }
+
+
+ /**
* Set timezone.
*
* @remarks Setting the timezone using getCalendar().setTimeZone()
@@ -192,4 +338,5 @@
public NumberFormat getNumberFormat() {
return formatter.getNumberFormat();
}
+
}
1.15 +21 -50 logging-log4j/src/java/org/apache/log4j/pattern/DatePatternConverter.java
Index: DatePatternConverter.java
===================================================================
RCS file: /home/cvs/logging-log4j/src/java/org/apache/log4j/pattern/DatePatternConverter.java,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -r1.14 -r1.15
--- DatePatternConverter.java 23 Dec 2004 22:49:11 -0000 1.14
+++ DatePatternConverter.java 24 Dec 2004 08:28:28 -0000 1.15
@@ -38,13 +38,12 @@
// We assume that each PatternConveter instance is unique within a layout,
// which is unique within an appender. We further assume that calls to the
// appender method are serialized (per appender).
- StringBuffer buf;
Logger logger = Logger.getLogger(DatePatternConverter.class);
- private DateFormat df;
- private Date date;
- protected FieldPosition pos = new FieldPosition(0);
- long lastTimestamp = 0;
- boolean alreadyWarned = false;
+ private CachedDateFormat df;
+ private final StringBuffer buf = new StringBuffer(30);
+ public static final String ISO8601_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS";
+ public static final String ABSOLUTE_PATTERN = "HH:mm:ss,SSS";
+ public static final String DATE_AND_TIME_PATTERN = "dd MMM yyyy HH:mm:ss,SSS";
// public DatePatternConverter(FormattingInfo formattingInfo) {
// super(formattingInfo);
@@ -52,8 +51,6 @@
// date = new Date();
// }
public DatePatternConverter() {
- this.buf = new StringBuffer(32);
- date = new Date();
}
/**
@@ -73,68 +70,42 @@
}
String pattern;
- if (patternOption == null) {
- pattern = "yyyy-MM-dd HH:mm:ss,SSS";
+ if (patternOption == null || patternOption.equalsIgnoreCase("ISO8601")) {
+ pattern = ISO8601_PATTERN;
} else if (
- patternOption.equalsIgnoreCase(AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT)) {
- pattern = "yyyy-MM-dd HH:mm:ss,SSS";
+ patternOption.equalsIgnoreCase("ABSOLUTE")) {
+ pattern = ABSOLUTE_PATTERN;
} else if (
- patternOption.equalsIgnoreCase(AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT)) {
- pattern = "HH:mm:ss,SSS";
- } else if (
- patternOption.equalsIgnoreCase(
- AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT)) {
- pattern = "dd MMM yyyy HH:mm:ss,SSS";
+ patternOption.equalsIgnoreCase("DATE")) {
+ pattern = DATE_AND_TIME_PATTERN;
} else {
pattern = patternOption;
}
+ SimpleDateFormat simpleFormat = null;
try {
- df = new SimpleDateFormat(pattern);
+ simpleFormat = new SimpleDateFormat(pattern);
} catch (IllegalArgumentException e) {
logger.warn(
"Could not instantiate SimpleDateFormat with pattern " + patternOption, e);
- // detault for the ISO8601 format
- pattern = "yyyy-MM-dd HH:mm:ss,SSS";
- df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");
+ // default to the ISO8601 format
+ simpleFormat = new SimpleDateFormat(ISO8601_PATTERN);
}
// if the option list contains a TZ option, then set it.
if (optionList != null && optionList.size() > 1) {
TimeZone tz = TimeZone.getTimeZone((String) optionList.get(1));
- df.setTimeZone(tz);
+ simpleFormat.setTimeZone(tz);
}
-
- // if we can cache, so much the better
- if(CacheUtil.isPatternSafeForCaching(pattern)) {
- df = new CachedDateFormat(df);
- }
+
+ df = new CachedDateFormat(simpleFormat, 1000);
}
public StringBuffer convert(LoggingEvent event) {
- long timestamp = event.getTimeStamp();
- // if called multiple times within the same milliseconds
- // return old value
- if(timestamp == lastTimestamp) {
- return buf;
- } else {
- buf.setLength(0);
- lastTimestamp = timestamp;
- date.setTime(timestamp);
- try {
- df.format(date, buf, pos);
- lastTimestamp = timestamp;
- } catch (Exception ex) {
- // this should never happen
- buf.append("DATE_CONV_ERROR");
- if(!alreadyWarned) {
- alreadyWarned = true;
- logger.error("Exception while converting date", ex);
- }
- }
- return buf;
- }
+ buf.setLength(0);
+ df.format(event.getTimeStamp(), buf);
+ return buf;
}
public String getName() {
---------------------------------------------------------------------
To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org
For additional commands, e-mail: log4j-dev-help@logging.apache.org