212
211
* The system timezone.
214
private volatile TimeZone timezone = null;
213
private static final TimeZone timezone;
218
217
* The time zone offset relative to GMT in text form when daylight saving
219
218
* is not in operation.
221
private volatile String timeZoneNoDST = null;
220
private static final String timeZoneNoDST;
225
224
* The time zone offset relative to GMT in text form when daylight saving
226
225
* is in operation.
228
private volatile String timeZoneDST = null;
227
private static final String timeZoneDST;
230
* The size of our global date format cache
232
private static final int globalCacheSize = 300;
235
* The size of our thread local date format cache
237
private static final int localCacheSize = 60;
232
241
* The current log file we are writing to. Helpful when checkExists
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);
247
* <p>Cache structure for formatted timestamps based on seconds.</p>
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>
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>
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>
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>
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>
269
* <p>This class uses a small thread local first level cache and a bigger
270
* synchronized global second level cache.</p>
272
private static class DateFormatCache {
274
private class Cache {
277
private static final String cLFFormat = "dd/MMM/yyyy:HH:mm:ss";
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 = "";
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();
293
private String cache[];
294
private SimpleDateFormat formatter;
295
private boolean isCLF = false;
297
private Cache parent = null;
299
private Cache(Cache parent) {
303
private Cache(String format, Cache parent) {
304
this(format, null, parent);
307
private Cache(String format, Locale loc, Cache parent) {
308
cache = new String[cacheSize];
309
for (int i = 0; i < cacheSize; i++) {
313
loc = cacheDefaultLocale;
315
if (format == null) {
318
formatter = new SimpleDateFormat(format, Locale.US);
320
formatter = new SimpleDateFormat(format, loc);
322
formatter.setTimeZone(TimeZone.getDefault());
323
this.parent = parent;
326
private String getFormatInternal(long time) {
328
long seconds = time / 1000;
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;
336
/* Second step: Try to locate in cache */
337
previousSeconds = seconds;
338
int index = (offset + (int)(seconds - first)) % cacheSize;
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;
349
/* Third step: not found in cache, adjust cache and add item */
350
} else if (seconds >= last + cacheSize || seconds <= first - cacheSize) {
352
last = first + cacheSize - 1;
355
for (int i = 1; i < cacheSize; i++) {
358
} else if (seconds > last) {
359
for (int i = 1; i < seconds - last; i++) {
360
cache[(index + cacheSize - i) % cacheSize] = null;
362
first = seconds - cacheSize;
364
} else if (seconds < first) {
365
for (int i = 1; i < first - seconds; i++) {
366
cache[(index + i) % cacheSize] = null;
369
last = seconds + cacheSize;
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);
379
currentDate.setTime(time);
380
previousFormat = formatter.format(currentDate);
382
StringBuilder current = new StringBuilder(32);
384
current.append(previousFormat);
386
current.append(getTimeZone(currentDate));
388
previousFormat = current.toString();
391
cache[index] = previousFormat;
392
return previousFormat;
396
/* Number of cached entries */
397
private int cacheSize = 0;
399
private Locale cacheDefaultLocale;
400
private DateFormatCache parent;
401
private Cache cLFCache;
402
private HashMap<String, Cache> formatCache = new HashMap<String, Cache>();
404
private DateFormatCache(int size, Locale loc, DateFormatCache parent) {
406
cacheDefaultLocale = loc;
407
this.parent = parent;
408
Cache parentCache = null;
409
if (parent != null) {
410
synchronized(parent) {
411
parentCache = parent.getCache(null, null);
414
cLFCache = new Cache(parentCache);
417
private Cache getCache(String format, Locale loc) {
419
if (format == null) {
422
cache = formatCache.get(format);
424
Cache parentCache = null;
425
if (parent != null) {
426
synchronized(parent) {
427
parentCache = parent.getCache(format, loc);
430
cache = new Cache(format, loc, parentCache);
431
formatCache.put(format, cache);
437
public String getFormat(long time) {
438
return cLFCache.getFormatInternal(time);
441
public String getFormat(String format, Locale loc, long time) {
442
return getCache(format, loc).getFormatInternal(time);
447
* Global date format cache.
449
private static final DateFormatCache globalDateCache =
450
new DateFormatCache(globalCacheSize, Locale.getDefault(), null);
453
* Thread local date format cache.
455
private static final ThreadLocal<DateFormatCache> localDateCache =
456
new ThreadLocal<DateFormatCache>() {
458
protected DateFormatCache initialValue() {
459
return new DateFormatCache(localCacheSize, Locale.getDefault(), globalDateCache);
253
465
* The system time when we last updated the Date that this valve
254
466
* uses for log lines.
256
private static final ThreadLocal<AccessDateStruct> currentDateStruct =
257
new ThreadLocal<AccessDateStruct>() {
468
private static final ThreadLocal<Date> localDate =
469
new ThreadLocal<Date>() {
259
protected AccessDateStruct initialValue() {
260
return new AccessDateStruct();
471
protected Date initialValue() {
477
* The list of our format types.
479
private static enum formatType {
480
CLF, SEC, MSEC, MSEC_FRAC, SDF
998
* write date and time, in Common Log Format - %t
1311
* write date and time, in configurable format (default CLF) - %t or %t{format}
1000
1313
protected class DateAndTimeElement implements AccessLogElement {
1316
* Format prefix specifying request start time
1318
private static final String requestStartPrefix = "begin";
1321
* Format prefix specifying response end time
1323
private static final String responseEndPrefix = "end";
1326
* Separator between optional prefix and rest of format
1328
private static final String prefixSeparator = ":";
1331
* Special format for seconds since epoch
1333
private static final String secFormat = "sec";
1336
* Special format for milliseconds since epoch
1338
private static final String msecFormat = "msec";
1341
* Special format for millisecond part of timestamp
1343
private static final String msecFractionFormat = "msec_frac";
1346
* The patterns we use to replace "S" and "SSS" millisecond
1347
* formatting of SimpleDateFormat by our own handling
1349
private static final String msecPattern = "{#}";
1350
private static final String trippleMsecPattern =
1351
msecPattern + msecPattern + msecPattern;
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;
1362
protected DateAndTimeElement() {
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
1373
private void tidyFormat() {
1374
boolean escape = false;
1375
StringBuilder result = new StringBuilder();
1376
int len = format.length();
1378
for (int i = 0; i < len; i++) {
1379
x = format.charAt(i);
1380
if (escape || x != 'S') {
1383
result.append(msecPattern);
1392
protected DateAndTimeElement(String header) {
1394
if (format != null) {
1395
if (format.equals(requestStartPrefix)) {
1398
} else if (format.startsWith(requestStartPrefix + prefixSeparator)) {
1400
format = format.substring(6);
1401
} else if (format.equals(responseEndPrefix)) {
1404
} else if (format.startsWith(responseEndPrefix + prefixSeparator)) {
1406
format = format.substring(4);
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;
1417
type = formatType.SDF;
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();
1022
buf.append(struct.currentDateString);
1426
long timestamp = date.getTime();
1433
buf.append(localDateCache.get().getFormat(timestamp));
1436
buf.append(timestamp / 1000);
1439
buf.append(timestamp);
1442
frac = timestamp % 1000;
1454
String temp = localDateCache.get().getFormat(format, locale, timestamp);
1456
frac = timestamp % 1000;
1457
StringBuilder trippleMsec = new StringBuilder(4);
1460
trippleMsec.append('0');
1461
trippleMsec.append('0');
1463
trippleMsec.append('0');
1466
trippleMsec.append(frac);
1467
temp = temp.replace(trippleMsecPattern, trippleMsec);
1468
temp = temp.replace(msecPattern, Long.toString(frac));