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