~ubuntu-branches/ubuntu/precise/tomcat7/precise-proposed

« back to all changes in this revision

Viewing changes to java/org/apache/catalina/valves/AccessLogValve.java

  • Committer: Bazaar Package Importer
  • Author(s): tony mancill
  • Date: 2011-07-25 22:58:33 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20110725225833-1t773ak3y3g9utm2
Tags: 7.0.19-1
* Team upload.
* New upstream release.
  - Includes fix for CVE-2011-2526 (Closes: #634992)
* Remove patch for CVE-2011-2204 (included upstream).

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 
22
22
import java.io.BufferedWriter;
23
23
import java.io.File;
24
 
import java.io.FileWriter;
 
24
import java.io.FileOutputStream;
25
25
import java.io.IOException;
 
26
import java.io.OutputStreamWriter;
26
27
import java.io.PrintWriter;
 
28
import java.io.UnsupportedEncodingException;
27
29
import java.net.InetAddress;
 
30
import java.nio.charset.Charset;
28
31
import java.text.SimpleDateFormat;
29
32
import java.util.ArrayList;
30
33
import java.util.Date;
31
34
import java.util.Enumeration;
 
35
import java.util.HashMap;
32
36
import java.util.Iterator;
33
37
import java.util.List;
 
38
import java.util.Locale;
34
39
import java.util.TimeZone;
35
40
 
36
41
import javax.servlet.ServletException;
47
52
import org.apache.juli.logging.Log;
48
53
import org.apache.juli.logging.LogFactory;
49
54
import org.apache.tomcat.util.ExceptionUtils;
 
55
import org.apache.tomcat.util.buf.B2CConverter;
50
56
 
51
57
 
52
58
/**
76
82
 * <li><b>%s</b> - HTTP status code of the response
77
83
 * <li><b>%S</b> - User session ID
78
84
 * <li><b>%t</b> - Date and time, in Common Log Format format
 
85
 * <li><b>%t{format}</b> - Date and time, in any format supported by SimpleDateFormat
79
86
 * <li><b>%u</b> - Remote user that was authenticated
80
87
 * <li><b>%U</b> - Requested URL path
81
88
 * <li><b>%v</b> - Local server name
117
124
 * @author Takayuki Kaneko
118
125
 * @author Peter Rossbach
119
126
 * 
120
 
 * @version $Id: AccessLogValve.java 1099553 2011-05-04 18:19:10Z markt $
 
127
 * @version $Id: AccessLogValve.java 1145237 2011-07-11 16:46:28Z kkolinko $
121
128
 */
122
129
 
123
130
public class AccessLogValve extends ValveBase implements AccessLog {
149
156
     * The descriptive information about this implementation.
150
157
     */
151
158
    protected static final String info =
152
 
        "org.apache.catalina.valves.AccessLogValve/2.1";
153
 
 
154
 
 
155
 
    /**
156
 
     * The set of month abbreviations for log messages.
157
 
     */
158
 
    protected static final String months[] =
159
 
    { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
160
 
      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
 
159
        "org.apache.catalina.valves.AccessLogValve/2.2";
161
160
 
162
161
 
163
162
    /**
211
210
    /**
212
211
     * The system timezone.
213
212
     */
214
 
    private volatile TimeZone timezone = null;
 
213
    private static final TimeZone timezone;
215
214
 
216
215
    
217
216
    /**
218
217
     * The time zone offset relative to GMT in text form when daylight saving
219
218
     * is not in operation.
220
219
     */
221
 
    private volatile String timeZoneNoDST = null;
 
220
    private static final String timeZoneNoDST;
222
221
 
223
222
 
224
223
    /**
225
224
     * The time zone offset relative to GMT in text form when daylight saving
226
225
     * is in operation.
227
226
     */
228
 
    private volatile String timeZoneDST = null;
 
227
    private static final String timeZoneDST;
229
228
    
 
229
    /**
 
230
     * The size of our global date format cache
 
231
     */
 
232
    private static final int globalCacheSize = 300;
 
233
 
 
234
    /**
 
235
     * The size of our thread local date format cache
 
236
     */
 
237
    private static final int localCacheSize = 60;
 
238
 
230
239
    
231
240
    /**
232
241
     * The current log file we are writing to. Helpful when checkExists
233
242
     * is true.
234
243
     */
235
244
    protected File currentLogFile = null;
236
 
    private static class AccessDateStruct {
237
 
        private Date currentDate = new Date();
238
 
        private String currentDateString = null;
239
 
        private SimpleDateFormat dayFormatter = new SimpleDateFormat("dd");
240
 
        private SimpleDateFormat monthFormatter = new SimpleDateFormat("MM");
241
 
        private SimpleDateFormat yearFormatter = new SimpleDateFormat("yyyy");
242
 
        private SimpleDateFormat timeFormatter = new SimpleDateFormat("HH:mm:ss");
243
 
        public AccessDateStruct() {
244
 
            TimeZone tz = TimeZone.getDefault();
245
 
            dayFormatter.setTimeZone(tz);
246
 
            monthFormatter.setTimeZone(tz);
247
 
            yearFormatter.setTimeZone(tz);
248
 
            timeFormatter.setTimeZone(tz);
 
245
 
 
246
    /**
 
247
     * <p>Cache structure for formatted timestamps based on seconds.</p>
 
248
     *
 
249
     * <p>The cache consists of entries for a consecutive range of
 
250
     * seconds. The length of the range is configurable. It is
 
251
     * implemented based on a cyclic buffer. New entries shift the range.</p>
 
252
     *
 
253
     * <p>There is one cache for the CLF format (the access log standard
 
254
     * format) and a HashMap of caches for additional formats used by
 
255
     * SimpleDateFormat.</p>
 
256
     *
 
257
     * <p>Although the cache supports specifying a locale when retrieving a
 
258
     * formatted timestamp, each format will always use the locale given
 
259
     * when the format was first used. New locales can only be used for new formats.
 
260
     * The CLF format will always be formatted using the locale
 
261
     * <code>en_US</code>.</p>
 
262
     *
 
263
     * <p>The cache is not threadsafe. It can be used without synchronization
 
264
     * via thread local instances, or with synchronization as a global cache.</p>
 
265
     *
 
266
     * <p>The cache can be created with a parent cache to build a cache hierarchy.
 
267
     * Access to the parent cache is threadsafe.</p>
 
268
     *
 
269
     * <p>This class uses a small thread local first level cache and a bigger
 
270
     * synchronized global second level cache.</p>
 
271
     */
 
272
    private static class DateFormatCache {
 
273
 
 
274
        private class Cache {
 
275
 
 
276
            /* CLF log format */
 
277
            private static final String cLFFormat = "dd/MMM/yyyy:HH:mm:ss";
 
278
 
 
279
            /* Second used to retrieve CLF format in most recent invocation */
 
280
            private long previousSeconds = 0L;
 
281
            /* Value of CLF format retrieved in most recent invocation */
 
282
            private String previousFormat = "";
 
283
 
 
284
            /* First second contained in cache */
 
285
            private long first = 0L;
 
286
            /* Last second contained in cache */
 
287
            private long last = 0L;
 
288
            /* Index of "first" in the cyclic cache */
 
289
            private int offset = 0;
 
290
            /* Helper object to be able to call SimpleDateFormat.format(). */
 
291
            private final Date currentDate = new Date();
 
292
 
 
293
            private String cache[];
 
294
            private SimpleDateFormat formatter;
 
295
            private boolean isCLF = false;
 
296
 
 
297
            private Cache parent = null;
 
298
 
 
299
            private Cache(Cache parent) {
 
300
                this(null, parent);
 
301
            }
 
302
 
 
303
            private Cache(String format, Cache parent) {
 
304
                this(format, null, parent);
 
305
            }
 
306
 
 
307
            private Cache(String format, Locale loc, Cache parent) {
 
308
                cache = new String[cacheSize];
 
309
                for (int i = 0; i < cacheSize; i++) {
 
310
                    cache[i] = null;
 
311
                }
 
312
                if (loc == null) {
 
313
                    loc = cacheDefaultLocale;
 
314
                }
 
315
                if (format == null) {
 
316
                    isCLF = true;
 
317
                    format = cLFFormat;
 
318
                    formatter = new SimpleDateFormat(format, Locale.US);
 
319
                } else {
 
320
                    formatter = new SimpleDateFormat(format, loc);
 
321
                }
 
322
                formatter.setTimeZone(TimeZone.getDefault());
 
323
                this.parent = parent;
 
324
            }
 
325
 
 
326
            private String getFormatInternal(long time) {
 
327
 
 
328
                long seconds = time / 1000;
 
329
 
 
330
                /* First step: if we have seen this timestamp
 
331
                   during the previous call, and we need CLF, return the previous value. */
 
332
                if (seconds == previousSeconds) {
 
333
                    return previousFormat;
 
334
                }
 
335
 
 
336
                /* Second step: Try to locate in cache */
 
337
                previousSeconds = seconds;
 
338
                int index = (offset + (int)(seconds - first)) % cacheSize;
 
339
                if (index < 0) {
 
340
                    index += cacheSize;
 
341
                }
 
342
                if (seconds >= first && seconds <= last) {
 
343
                    if (cache[index] != null) {
 
344
                        /* Found, so remember for next call and return.*/
 
345
                        previousFormat = cache[index];
 
346
                        return previousFormat;
 
347
                    }
 
348
 
 
349
                /* Third step: not found in cache, adjust cache and add item */
 
350
                } else if (seconds >= last + cacheSize || seconds <= first - cacheSize) {
 
351
                    first = seconds;
 
352
                    last = first + cacheSize - 1;
 
353
                    index = 0;
 
354
                    offset = 0;
 
355
                    for (int i = 1; i < cacheSize; i++) {
 
356
                        cache[i] = null;
 
357
                    }
 
358
                } else if (seconds > last) {
 
359
                    for (int i = 1; i < seconds - last; i++) {
 
360
                        cache[(index + cacheSize - i) % cacheSize] = null;
 
361
                    }
 
362
                    first = seconds - cacheSize;
 
363
                    last = seconds;
 
364
                } else if (seconds < first) {
 
365
                    for (int i = 1; i < first - seconds; i++) {
 
366
                        cache[(index + i) % cacheSize] = null;
 
367
                    }
 
368
                    first = seconds;
 
369
                    last = seconds + cacheSize;
 
370
                }
 
371
 
 
372
                /* Last step: format new timestamp either using
 
373
                 * parent cache or locally. */
 
374
                if (parent != null) {
 
375
                    synchronized(parent) {
 
376
                        previousFormat = parent.getFormatInternal(time);
 
377
                    }
 
378
                } else {
 
379
                    currentDate.setTime(time);
 
380
                    previousFormat = formatter.format(currentDate);
 
381
                    if (isCLF) {
 
382
                        StringBuilder current = new StringBuilder(32);
 
383
                        current.append('[');
 
384
                        current.append(previousFormat);
 
385
                        current.append(' ');
 
386
                        current.append(getTimeZone(currentDate));
 
387
                        current.append(']');
 
388
                        previousFormat = current.toString();
 
389
                    }
 
390
                }
 
391
                cache[index] = previousFormat;
 
392
                return previousFormat;
 
393
            }
 
394
        }
 
395
 
 
396
        /* Number of cached entries */
 
397
        private int cacheSize = 0;
 
398
 
 
399
        private Locale cacheDefaultLocale;
 
400
        private DateFormatCache parent;
 
401
        private Cache cLFCache;
 
402
        private HashMap<String, Cache> formatCache = new HashMap<String, Cache>();
 
403
 
 
404
        private DateFormatCache(int size, Locale loc, DateFormatCache parent) {
 
405
            cacheSize = size;
 
406
            cacheDefaultLocale = loc;
 
407
            this.parent = parent;
 
408
            Cache parentCache = null;
 
409
            if (parent != null) {
 
410
                synchronized(parent) {
 
411
                    parentCache = parent.getCache(null, null);
 
412
                }
 
413
            }
 
414
            cLFCache = new Cache(parentCache);
 
415
        }
 
416
 
 
417
        private Cache getCache(String format, Locale loc) {
 
418
            Cache cache;
 
419
            if (format == null) {
 
420
                cache = cLFCache;
 
421
            } else {
 
422
                cache = formatCache.get(format);
 
423
                if (cache == null) {
 
424
                    Cache parentCache = null;
 
425
                    if (parent != null) {
 
426
                        synchronized(parent) {
 
427
                            parentCache = parent.getCache(format, loc);
 
428
                        }
 
429
                    }
 
430
                    cache = new Cache(format, loc, parentCache);
 
431
                    formatCache.put(format, cache);
 
432
                }
 
433
            }
 
434
            return cache;
 
435
        }
 
436
 
 
437
        public String getFormat(long time) {
 
438
            return cLFCache.getFormatInternal(time);
 
439
        }
 
440
 
 
441
        public String getFormat(String format, Locale loc, long time) {
 
442
            return getCache(format, loc).getFormatInternal(time);
249
443
        }
250
444
    }
251
 
    
 
445
 
 
446
    /**
 
447
     * Global date format cache.
 
448
     */
 
449
    private static final DateFormatCache globalDateCache =
 
450
            new DateFormatCache(globalCacheSize, Locale.getDefault(), null);
 
451
 
 
452
    /**
 
453
     * Thread local date format cache.
 
454
     */
 
455
    private static final ThreadLocal<DateFormatCache> localDateCache =
 
456
            new ThreadLocal<DateFormatCache>() {
 
457
        @Override
 
458
        protected DateFormatCache initialValue() {
 
459
            return new DateFormatCache(localCacheSize, Locale.getDefault(), globalDateCache);
 
460
        }
 
461
    };
 
462
 
 
463
 
252
464
    /**
253
465
     * The system time when we last updated the Date that this valve
254
466
     * uses for log lines.
255
467
     */
256
 
    private static final ThreadLocal<AccessDateStruct> currentDateStruct =
257
 
            new ThreadLocal<AccessDateStruct>() {
 
468
    private static final ThreadLocal<Date> localDate =
 
469
            new ThreadLocal<Date>() {
258
470
        @Override
259
 
        protected AccessDateStruct initialValue() {
260
 
            return new AccessDateStruct();
 
471
        protected Date initialValue() {
 
472
            return new Date();
261
473
        }
262
474
    };
 
475
 
 
476
    /**
 
477
     * The list of our format types.
 
478
     */
 
479
    private static enum formatType {
 
480
        CLF, SEC, MSEC, MSEC_FRAC, SDF
 
481
    }
 
482
 
263
483
    /**
264
484
     * Resolve hosts.
265
485
     */
288
508
     * Date format to place in log file name. Use at your own risk!
289
509
     */
290
510
    protected String fileDateFormat = null;
291
 
    
 
511
 
 
512
 
 
513
    /**
 
514
     * Name of locale used to format timestamps in log entries and in
 
515
     * log file name suffix.
 
516
     */
 
517
    protected String localeName = Locale.getDefault().toString();
 
518
 
 
519
 
 
520
    /**
 
521
     * Locale used to format timestamps in log entries and in
 
522
     * log file name suffix.
 
523
     */
 
524
    protected Locale locale = Locale.getDefault();
 
525
 
 
526
    /**
 
527
     * Character set used by the log file. If it is <code>null</code>, the
 
528
     * system default character set will be used. An empty string will be
 
529
     * treated as <code>null</code> when this property is assigned.
 
530
     */
 
531
    protected String encoding = null;
 
532
 
292
533
    /**
293
534
     * Array of AccessLogElement, they will be used to make log message.
294
535
     */
517
758
        this.condition = condition;
518
759
    }
519
760
 
 
761
 
520
762
    /**
521
763
     *  Return the date format date based log rotation.
522
764
     */
532
774
        this.fileDateFormat =  fileDateFormat;
533
775
    }
534
776
 
 
777
 
 
778
    /**
 
779
     * Return the locale used to format timestamps in log entries and in
 
780
     * log file name suffix.
 
781
     */
 
782
    public String getLocale() {
 
783
        return localeName;
 
784
    }
 
785
 
 
786
 
 
787
    /**
 
788
     * Set the locale used to format timestamps in log entries and in
 
789
     * log file name suffix. Changing the locale is only supported
 
790
     * as long as the AccessLogValve has not logged anything. Changing
 
791
     * the locale later can lead to inconsistent formatting.
 
792
     *
 
793
     * @param localeName The locale to use.
 
794
     */
 
795
    public void setLocale(String localeName) {
 
796
        this.localeName = localeName;
 
797
        locale = findLocale(localeName, locale);
 
798
    }
 
799
 
 
800
    /**
 
801
     * Return the character set name that is used to write the log file.
 
802
     *
 
803
     * @return Character set name, or <code>null</code> if the system default
 
804
     *  character set is used.
 
805
     */
 
806
    public String getEncoding() {
 
807
        return encoding;
 
808
    }
 
809
 
 
810
    /**
 
811
     * Set the character set that is used to write the log file.
 
812
     * 
 
813
     * @param encoding The name of the character set.
 
814
     */
 
815
    public void setEncoding(String encoding) {
 
816
        if (encoding != null && encoding.length() > 0) {
 
817
            this.encoding = encoding;
 
818
        } else {
 
819
            this.encoding = null;
 
820
        }
 
821
    }
 
822
 
535
823
    // --------------------------------------------------------- Public Methods
536
824
 
537
825
    /**
572
860
            return;
573
861
        }
574
862
 
575
 
        Date date = getDate();
 
863
        /**
 
864
         * XXX This is a bit silly, but we want to have start and stop time and
 
865
         * duration consistent. It would be better to keep start and stop
 
866
         * simply in the request and/or response object and remove time
 
867
         * (duration) from the interface.
 
868
         */
 
869
        long start = request.getCoyoteRequest().getStartTime();
 
870
        Date date = getDate(start + time);
 
871
 
576
872
        StringBuilder result = new StringBuilder(128);
577
873
 
578
874
        for (int i = 0; i < logElements.length; i++) {
698
994
 
699
995
 
700
996
    /**
701
 
     * Return the month abbreviation for the specified month, which must
702
 
     * be a two-digit String.
703
 
     *
704
 
     * @param month Month number ("01" .. "12").
705
 
     */
706
 
    private String lookup(String month) {
707
 
        int index;
708
 
        try {
709
 
            index = Integer.parseInt(month) - 1;
710
 
        } catch (Throwable t) {
711
 
            ExceptionUtils.handleThrowable(t);
712
 
            index = 0;  // Can not happen, in theory
713
 
        }
714
 
        return (months[index]);
715
 
    }
716
 
 
717
 
 
718
 
    /**
719
997
     * Open the new log file for the date specified by <code>dateStamp</code>.
720
998
     */
721
999
    protected synchronized void open() {
730
1008
        }
731
1009
 
732
1010
        // Open the current log file
 
1011
        File pathname;
 
1012
        // If no rotate - no need for dateStamp in fileName
 
1013
        if (rotatable) {
 
1014
            pathname = new File(dir.getAbsoluteFile(), prefix + dateStamp
 
1015
                    + suffix);
 
1016
        } else {
 
1017
            pathname = new File(dir.getAbsoluteFile(), prefix + suffix);
 
1018
        }
 
1019
        File parent = pathname.getParentFile();
 
1020
        if (!parent.exists()) {
 
1021
            if (!parent.mkdirs()) {
 
1022
                log.error(sm.getString("accessLogValve.openDirFail", parent));
 
1023
            }
 
1024
        }
 
1025
 
 
1026
        Charset charset = null;
 
1027
        if (encoding != null) {
 
1028
            try {
 
1029
                charset = B2CConverter.getCharset(encoding);
 
1030
            } catch (UnsupportedEncodingException ex) {
 
1031
                log.error(sm.getString(
 
1032
                        "accessLogValve.unsupportedEncoding", encoding), ex);
 
1033
            }
 
1034
        }
 
1035
        if (charset == null) {
 
1036
            charset = Charset.defaultCharset();
 
1037
        }
 
1038
 
733
1039
        try {
734
 
            String pathname;
735
 
            // If no rotate - no need for dateStamp in fileName
736
 
            if (rotatable) {
737
 
                pathname = dir.getAbsolutePath() + File.separator + prefix
738
 
                        + dateStamp + suffix;
739
 
            } else {
740
 
                pathname = dir.getAbsolutePath() + File.separator + prefix
741
 
                        + suffix;
742
 
            }
743
 
            writer = new PrintWriter(new BufferedWriter(new FileWriter(
744
 
                    pathname, true), 128000), false);
745
 
            
746
 
            currentLogFile = new File(pathname);
 
1040
            writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
 
1041
                    new FileOutputStream(pathname, true), charset), 128000),
 
1042
                    false);
 
1043
 
 
1044
            currentLogFile = pathname;
747
1045
        } catch (IOException e) {
748
1046
            writer = null;
749
1047
            currentLogFile = null;
 
1048
            log.error(sm.getString("accessLogValve.openFail", pathname), e);
750
1049
        }
751
1050
    }
752
1051
 
759
1058
     * 
760
1059
     * @return Date
761
1060
     */
762
 
    private Date getDate() {
763
 
        // Only create a new Date once per second, max.
764
 
        long systime = System.currentTimeMillis();
765
 
        AccessDateStruct struct = currentDateStruct.get(); 
766
 
        if ((systime - struct.currentDate.getTime()) > 1000) {
767
 
            struct.currentDate.setTime(systime);
768
 
            struct.currentDateString = null;
769
 
        }
770
 
        return struct.currentDate;
 
1061
    private static Date getDate(long systime) {
 
1062
        Date date = localDate.get();
 
1063
        date.setTime(systime);
 
1064
        return date;
771
1065
    }
772
1066
 
773
1067
 
774
 
    private String getTimeZone(Date date) {
 
1068
    private static String getTimeZone(Date date) {
775
1069
        if (timezone.inDaylightTime(date)) {
776
1070
            return timeZoneDST;
777
1071
        } else {
780
1074
    }
781
1075
    
782
1076
    
783
 
    private String calculateTimeZoneOffset(long offset) {
 
1077
    private static String calculateTimeZoneOffset(long offset) {
784
1078
        StringBuilder tz = new StringBuilder();
785
1079
        if ((offset < 0)) {
786
1080
            tz.append("-");
803
1097
        return tz.toString();
804
1098
    }
805
1099
 
 
1100
    /**
 
1101
     * Find a locale by name
 
1102
     */
 
1103
    protected static Locale findLocale(String name, Locale fallback) {
 
1104
        if (name == null || name.isEmpty()) {
 
1105
            return Locale.getDefault();
 
1106
        } else {
 
1107
            for (Locale l: Locale.getAvailableLocales()) {
 
1108
                if (name.equals(l.toString())) {
 
1109
                    return(l);
 
1110
                }
 
1111
            }
 
1112
        }
 
1113
        log.error(sm.getString("accessLogValve.invalidLocale", name));
 
1114
        return fallback;
 
1115
    }
 
1116
 
 
1117
    static {
 
1118
        // Initialize the timeZone
 
1119
        timezone = TimeZone.getDefault();
 
1120
        timeZoneNoDST = calculateTimeZoneOffset(timezone.getRawOffset());
 
1121
        int offset = timezone.getDSTSavings();
 
1122
        timeZoneDST = calculateTimeZoneOffset(timezone.getRawOffset() + offset);
 
1123
    }
 
1124
 
806
1125
 
807
1126
    /**
808
1127
     * Start this component and implement the requirements
814
1133
    @Override
815
1134
    protected synchronized void startInternal() throws LifecycleException {
816
1135
 
817
 
        // Initialize the timeZone, Date formatters, and currentDate
818
 
        TimeZone tz = TimeZone.getDefault();
819
 
        timezone = tz;
820
 
        timeZoneNoDST = calculateTimeZoneOffset(tz.getRawOffset());
821
 
        int offset = tz.getDSTSavings();
822
 
        timeZoneDST = calculateTimeZoneOffset(tz.getRawOffset() + offset);
823
 
 
 
1136
        // Initialize the Date formatters
824
1137
        String format = getFileDateFormat();
825
1138
        if (format == null || format.length() == 0) {
826
1139
            format = "yyyy-MM-dd";
827
1140
            setFileDateFormat(format);
828
1141
        }
829
 
        fileDateFormatter = new SimpleDateFormat(format);
830
 
        fileDateFormatter.setTimeZone(tz);
831
 
        dateStamp = fileDateFormatter.format(currentDateStruct.get().currentDate);
 
1142
        fileDateFormatter = new SimpleDateFormat(format, Locale.US);
 
1143
        fileDateFormatter.setTimeZone(timezone);
 
1144
        dateStamp = fileDateFormatter.format(new Date(System.currentTimeMillis()));
832
1145
        open();
833
1146
        
834
1147
        setState(LifecycleState.STARTING);
995
1308
    }
996
1309
 
997
1310
    /**
998
 
     * write date and time, in Common Log Format - %t
 
1311
     * write date and time, in configurable format (default CLF) - %t or %t{format}
999
1312
     */
1000
1313
    protected class DateAndTimeElement implements AccessLogElement {
1001
1314
 
 
1315
        /**
 
1316
         * Format prefix specifying request start time
 
1317
         */
 
1318
        private static final String requestStartPrefix = "begin";
 
1319
 
 
1320
        /**
 
1321
         * Format prefix specifying response end time
 
1322
         */
 
1323
        private static final String responseEndPrefix = "end";
 
1324
 
 
1325
        /**
 
1326
         * Separator between optional prefix and rest of format
 
1327
         */
 
1328
        private static final String prefixSeparator = ":";
 
1329
 
 
1330
        /**
 
1331
         * Special format for seconds since epoch
 
1332
         */
 
1333
        private static final String secFormat = "sec";
 
1334
 
 
1335
        /**
 
1336
         * Special format for milliseconds since epoch
 
1337
         */
 
1338
        private static final String msecFormat = "msec";
 
1339
 
 
1340
        /**
 
1341
         * Special format for millisecond part of timestamp
 
1342
         */
 
1343
        private static final String msecFractionFormat = "msec_frac";
 
1344
 
 
1345
        /**
 
1346
         * The patterns we use to replace "S" and "SSS" millisecond
 
1347
         * formatting of SimpleDateFormat by our own handling
 
1348
         */
 
1349
        private static final String msecPattern = "{#}";
 
1350
        private static final String trippleMsecPattern =
 
1351
            msecPattern + msecPattern + msecPattern;
 
1352
 
 
1353
        /* Our format description string, null if CLF */
 
1354
        private String format = null;
 
1355
        /* Whether to use begin of request or end of response as the timestamp */
 
1356
        private boolean usesBegin = false;
 
1357
        /* The format type */
 
1358
        private formatType type = formatType.CLF;
 
1359
        /* Whether we need to postprocess by adding milliseconds */
 
1360
        private boolean usesMsecs = false;
 
1361
 
 
1362
        protected DateAndTimeElement() {
 
1363
            this(null);
 
1364
        }
 
1365
 
 
1366
        /**
 
1367
         * Replace the millisecond formatting character 'S' by
 
1368
         * some dummy characters in order to make the resulting
 
1369
         * formatted time stamps cacheable. We replace the dummy
 
1370
         * chars later with the actual milliseconds because that's
 
1371
         * relatively cheap.
 
1372
         */
 
1373
        private void tidyFormat() {
 
1374
            boolean escape = false;
 
1375
            StringBuilder result = new StringBuilder();
 
1376
            int len = format.length();
 
1377
            char x;
 
1378
            for (int i = 0; i < len; i++) {
 
1379
                x = format.charAt(i);
 
1380
                if (escape || x != 'S') {
 
1381
                    result.append(x);
 
1382
                } else {
 
1383
                    result.append(msecPattern);
 
1384
                    usesMsecs = true;
 
1385
                }
 
1386
                if (x == '\'') {
 
1387
                    escape = !escape;
 
1388
                }
 
1389
            }
 
1390
        }
 
1391
 
 
1392
        protected DateAndTimeElement(String header) {
 
1393
            format = header;
 
1394
            if (format != null) {
 
1395
                if (format.equals(requestStartPrefix)) {
 
1396
                    usesBegin = true;
 
1397
                    format = "";
 
1398
                } else if (format.startsWith(requestStartPrefix + prefixSeparator)) {
 
1399
                    usesBegin = true;
 
1400
                    format = format.substring(6);
 
1401
                } else if (format.equals(responseEndPrefix)) {
 
1402
                    usesBegin = false;
 
1403
                    format = "";
 
1404
                } else if (format.startsWith(responseEndPrefix + prefixSeparator)) {
 
1405
                    usesBegin = false;
 
1406
                    format = format.substring(4);
 
1407
                }
 
1408
                if (format.length() == 0) {
 
1409
                    type = formatType.CLF;
 
1410
                } else if (format.equals(secFormat)) {
 
1411
                    type = formatType.SEC;
 
1412
                } else if (format.equals(msecFormat)) {
 
1413
                    type = formatType.MSEC;
 
1414
                } else if (format.equals(msecFractionFormat)) {
 
1415
                    type = formatType.MSEC_FRAC;
 
1416
                } else {
 
1417
                    type = formatType.SDF;
 
1418
                    tidyFormat();
 
1419
                }
 
1420
            }
 
1421
        }
1002
1422
 
1003
1423
        @Override
1004
1424
        public void addElement(StringBuilder buf, Date date, Request request,
1005
1425
                Response response, long time) {
1006
 
            AccessDateStruct struct = currentDateStruct.get();
1007
 
            if (struct.currentDateString == null) {
1008
 
                StringBuilder current = new StringBuilder(32);
1009
 
                current.append('[');
1010
 
                current.append(struct.dayFormatter.format(date));
1011
 
                current.append('/');
1012
 
                current.append(lookup(struct.monthFormatter.format(date)));
1013
 
                current.append('/');
1014
 
                current.append(struct.yearFormatter.format(date));
1015
 
                current.append(':');
1016
 
                current.append(struct.timeFormatter.format(date));
1017
 
                current.append(' ');
1018
 
                current.append(getTimeZone(date));
1019
 
                current.append(']');
1020
 
                struct.currentDateString = current.toString();
1021
 
            }
1022
 
            buf.append(struct.currentDateString);
 
1426
            long timestamp = date.getTime();
 
1427
            long frac;
 
1428
            if (usesBegin) {
 
1429
                timestamp -= time;
 
1430
            }
 
1431
            switch (type) {
 
1432
            case CLF:
 
1433
                buf.append(localDateCache.get().getFormat(timestamp));
 
1434
                break;
 
1435
            case SEC:
 
1436
                buf.append(timestamp / 1000);
 
1437
                break;
 
1438
            case MSEC:
 
1439
                buf.append(timestamp);
 
1440
                break;
 
1441
            case MSEC_FRAC:
 
1442
                frac = timestamp % 1000;
 
1443
                if (frac < 100) {
 
1444
                    if (frac < 10) {
 
1445
                        buf.append('0');
 
1446
                        buf.append('0');
 
1447
                    } else {
 
1448
                        buf.append('0');
 
1449
                    }
 
1450
                }
 
1451
                buf.append(frac);
 
1452
                break;
 
1453
            case SDF:
 
1454
                String temp = localDateCache.get().getFormat(format, locale, timestamp);
 
1455
                if (usesMsecs) {
 
1456
                    frac = timestamp % 1000;
 
1457
                    StringBuilder trippleMsec = new StringBuilder(4);
 
1458
                    if (frac < 100) {
 
1459
                        if (frac < 10) {
 
1460
                            trippleMsec.append('0');
 
1461
                            trippleMsec.append('0');
 
1462
                        } else {
 
1463
                            trippleMsec.append('0');
 
1464
                        }
 
1465
                    }
 
1466
                    trippleMsec.append(frac);
 
1467
                    temp = temp.replace(trippleMsecPattern, trippleMsec);
 
1468
                    temp = temp.replace(msecPattern, Long.toString(frac));
 
1469
                }
 
1470
                buf.append(temp);
 
1471
                break;
 
1472
            }
1023
1473
        }
1024
1474
    }
1025
1475
 
1447
1897
            return new RequestAttributeElement(header);
1448
1898
        case 's':
1449
1899
            return new SessionAttributeElement(header);            
 
1900
        case 't':
 
1901
            return new DateAndTimeElement(header);
1450
1902
        default:
1451
1903
            return new StringElement("???");
1452
1904
        }