001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.pattern; 018 019import java.util.Arrays; 020import java.util.Date; 021import java.util.Objects; 022import java.util.TimeZone; 023import java.util.concurrent.atomic.AtomicReference; 024 025import org.apache.logging.log4j.core.LogEvent; 026import org.apache.logging.log4j.core.config.plugins.Plugin; 027import org.apache.logging.log4j.core.util.Constants; 028import org.apache.logging.log4j.core.util.datetime.FastDateFormat; 029import org.apache.logging.log4j.core.util.datetime.FixedDateFormat; 030import org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat; 031 032/** 033 * Converts and formats the event's date in a StringBuilder. 034 */ 035@Plugin(name = "DatePatternConverter", category = PatternConverter.CATEGORY) 036@ConverterKeys({"d", "date"}) 037public final class DatePatternConverter extends LogEventPatternConverter implements ArrayPatternConverter { 038 039 private abstract static class Formatter { 040 long previousTime; // for ThreadLocal caching mode 041 042 abstract String format(long timeMillis); 043 044 abstract void formatToBuffer(long timeMillis, StringBuilder destination); 045 046 public String toPattern() { 047 return null; 048 } 049 } 050 051 private static final class PatternFormatter extends Formatter { 052 private final FastDateFormat fastDateFormat; 053 054 // this field is only used in ThreadLocal caching mode 055 private final StringBuilder cachedBuffer = new StringBuilder(64); 056 057 PatternFormatter(final FastDateFormat fastDateFormat) { 058 this.fastDateFormat = fastDateFormat; 059 } 060 061 @Override 062 String format(final long timeMillis) { 063 return fastDateFormat.format(timeMillis); 064 } 065 066 @Override 067 void formatToBuffer(final long timeMillis, final StringBuilder destination) { 068 if (previousTime != timeMillis) { 069 cachedBuffer.setLength(0); 070 fastDateFormat.format(timeMillis, cachedBuffer); 071 } 072 destination.append(cachedBuffer); 073 } 074 075 @Override 076 public String toPattern() { 077 return fastDateFormat.toPattern(); 078 } 079 } 080 081 private static final class FixedFormatter extends Formatter { 082 private final FixedDateFormat fixedDateFormat; 083 084 // below fields are only used in ThreadLocal caching mode 085 private final char[] cachedBuffer = new char[64]; // max length of formatted date-time in any format < 64 086 private int length = 0; 087 088 FixedFormatter(final FixedDateFormat fixedDateFormat) { 089 this.fixedDateFormat = fixedDateFormat; 090 } 091 092 @Override 093 String format(final long timeMillis) { 094 return fixedDateFormat.format(timeMillis); 095 } 096 097 @Override 098 void formatToBuffer(final long timeMillis, final StringBuilder destination) { 099 if (previousTime != timeMillis) { 100 length = fixedDateFormat.format(timeMillis, cachedBuffer, 0); 101 } 102 destination.append(cachedBuffer, 0, length); 103 } 104 105 @Override 106 public String toPattern() { 107 return fixedDateFormat.getFormat(); 108 } 109 } 110 111 private static final class UnixFormatter extends Formatter { 112 113 @Override 114 String format(final long timeMillis) { 115 return Long.toString(timeMillis / 1000); 116 } 117 118 @Override 119 void formatToBuffer(final long timeMillis, final StringBuilder destination) { 120 destination.append(timeMillis / 1000); // no need for caching 121 } 122 } 123 124 private static final class UnixMillisFormatter extends Formatter { 125 126 @Override 127 String format(final long timeMillis) { 128 return Long.toString(timeMillis); 129 } 130 131 @Override 132 void formatToBuffer(final long timeMillis, final StringBuilder destination) { 133 destination.append(timeMillis); // no need for caching 134 } 135 } 136 137 private final class CachedTime { 138 public long timestampMillis; 139 public String formatted; 140 141 public CachedTime(final long timestampMillis) { 142 this.timestampMillis = timestampMillis; 143 this.formatted = formatter.format(this.timestampMillis); 144 } 145 } 146 147 /** 148 * UNIX formatter in seconds (standard). 149 */ 150 private static final String UNIX_FORMAT = "UNIX"; 151 152 /** 153 * UNIX formatter in milliseconds 154 */ 155 private static final String UNIX_MILLIS_FORMAT = "UNIX_MILLIS"; 156 157 private final String[] options; 158 private final ThreadLocal<Formatter> threadLocalFormatter = new ThreadLocal<>(); 159 private final AtomicReference<CachedTime> cachedTime; 160 private final Formatter formatter; 161 162 /** 163 * Private constructor. 164 * 165 * @param options options, may be null. 166 */ 167 private DatePatternConverter(final String[] options) { 168 super("Date", "date"); 169 this.options = options == null ? null : Arrays.copyOf(options, options.length); 170 this.formatter = createFormatter(options); 171 cachedTime = new AtomicReference<>(new CachedTime(System.currentTimeMillis())); 172 } 173 174 private Formatter createFormatter(final String[] options) { 175 final FixedDateFormat fixedDateFormat = FixedDateFormat.createIfSupported(options); 176 if (fixedDateFormat != null) { 177 return createFixedFormatter(fixedDateFormat); 178 } 179 return createNonFixedFormatter(options); 180 } 181 182 /** 183 * Obtains an instance of pattern converter. 184 * 185 * @param options options, may be null. 186 * @return instance of pattern converter. 187 */ 188 public static DatePatternConverter newInstance(final String[] options) { 189 return new DatePatternConverter(options); 190 } 191 192 private static Formatter createFixedFormatter(final FixedDateFormat fixedDateFormat) { 193 return new FixedFormatter(fixedDateFormat); 194 } 195 196 private static Formatter createNonFixedFormatter(final String[] options) { 197 // if we get here, options is a non-null array with at least one element (first of which non-null) 198 Objects.requireNonNull(options); 199 if (options.length == 0) { 200 throw new IllegalArgumentException("options array must have at least one element"); 201 } 202 Objects.requireNonNull(options[0]); 203 final String patternOption = options[0]; 204 if (UNIX_FORMAT.equals(patternOption)) { 205 return new UnixFormatter(); 206 } 207 if (UNIX_MILLIS_FORMAT.equals(patternOption)) { 208 return new UnixMillisFormatter(); 209 } 210 // LOG4J2-1149: patternOption may be a name (if a time zone was specified) 211 final FixedDateFormat.FixedFormat fixedFormat = FixedDateFormat.FixedFormat.lookup(patternOption); 212 final String pattern = fixedFormat == null ? patternOption : fixedFormat.getPattern(); 213 214 // if the option list contains a TZ option, then set it. 215 TimeZone tz = null; 216 if (options.length > 1 && options[1] != null) { 217 tz = TimeZone.getTimeZone(options[1]); 218 } 219 220 try { 221 final FastDateFormat tempFormat = FastDateFormat.getInstance(pattern, tz); 222 return new PatternFormatter(tempFormat); 223 } catch (final IllegalArgumentException e) { 224 LOGGER.warn("Could not instantiate FastDateFormat with pattern " + pattern, e); 225 226 // default to the DEFAULT format 227 return createFixedFormatter(FixedDateFormat.create(FixedFormat.DEFAULT)); 228 } 229 } 230 231 /** 232 * Appends formatted date to string buffer. 233 * 234 * @param date date 235 * @param toAppendTo buffer to which formatted date is appended. 236 */ 237 public void format(final Date date, final StringBuilder toAppendTo) { 238 format(date.getTime(), toAppendTo); 239 } 240 241 /** 242 * {@inheritDoc} 243 */ 244 @Override 245 public void format(final LogEvent event, final StringBuilder output) { 246 format(event.getTimeMillis(), output); 247 } 248 249 public void format(final long timestampMillis, final StringBuilder output) { 250 if (Constants.ENABLE_THREADLOCALS) { 251 formatWithoutAllocation(timestampMillis, output); 252 } else { 253 formatWithoutThreadLocals(timestampMillis, output); 254 } 255 } 256 257 private void formatWithoutAllocation(final long timestampMillis, final StringBuilder output) { 258 final Formatter formatter = getThreadLocalFormatter(); 259 formatter.formatToBuffer(timestampMillis, output); 260 } 261 262 private Formatter getThreadLocalFormatter() { 263 Formatter result = threadLocalFormatter.get(); 264 if (result == null) { 265 result = createFormatter(options); 266 threadLocalFormatter.set(result); 267 } 268 return result; 269 } 270 271 private void formatWithoutThreadLocals(final long timestampMillis, final StringBuilder output) { 272 CachedTime cached = cachedTime.get(); 273 if (timestampMillis != cached.timestampMillis) { 274 final CachedTime newTime = new CachedTime(timestampMillis); 275 if (cachedTime.compareAndSet(cached, newTime)) { 276 cached = newTime; 277 } else { 278 cached = cachedTime.get(); 279 } 280 } 281 output.append(cached.formatted); 282 } 283 284 /** 285 * {@inheritDoc} 286 */ 287 @Override 288 public void format(final Object obj, final StringBuilder output) { 289 if (obj instanceof Date) { 290 format((Date) obj, output); 291 } 292 super.format(obj, output); 293 } 294 295 @Override 296 public void format(final StringBuilder toAppendTo, final Object... objects) { 297 for (final Object obj : objects) { 298 if (obj instanceof Date) { 299 format(obj, toAppendTo); 300 break; 301 } 302 } 303 } 304 305 /** 306 * Gets the pattern string describing this date format. 307 * 308 * @return the pattern string describing this date format. 309 */ 310 public String getPattern() { 311 return formatter.toPattern(); 312 } 313 314}