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.time.Instant; 029import org.apache.logging.log4j.core.time.MutableInstant; 030import org.apache.logging.log4j.core.util.datetime.FastDateFormat; 031import org.apache.logging.log4j.core.util.datetime.FixedDateFormat; 032import org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat; 033import org.apache.logging.log4j.util.PerformanceSensitive; 034 035/** 036 * Converts and formats the event's date in a StringBuilder. 037 */ 038@Plugin(name = "DatePatternConverter", category = PatternConverter.CATEGORY) 039@ConverterKeys({"d", "date"}) 040@PerformanceSensitive("allocation") 041public final class DatePatternConverter extends LogEventPatternConverter implements ArrayPatternConverter { 042 043 private abstract static class Formatter { 044 long previousTime; // for ThreadLocal caching mode 045 int nanos; 046 047 abstract String format(final Instant instant); 048 049 abstract void formatToBuffer(final Instant instant, StringBuilder destination); 050 051 public String toPattern() { 052 return null; 053 } 054 } 055 056 private static final class PatternFormatter extends Formatter { 057 private final FastDateFormat fastDateFormat; 058 059 // this field is only used in ThreadLocal caching mode 060 private final StringBuilder cachedBuffer = new StringBuilder(64); 061 062 PatternFormatter(final FastDateFormat fastDateFormat) { 063 this.fastDateFormat = fastDateFormat; 064 } 065 066 @Override 067 String format(final Instant instant) { 068 return fastDateFormat.format(instant.getEpochMillisecond()); 069 } 070 071 @Override 072 void formatToBuffer(final Instant instant, final StringBuilder destination) { 073 final long timeMillis = instant.getEpochMillisecond(); 074 if (previousTime != timeMillis) { 075 cachedBuffer.setLength(0); 076 fastDateFormat.format(timeMillis, cachedBuffer); 077 } 078 destination.append(cachedBuffer); 079 } 080 081 @Override 082 public String toPattern() { 083 return fastDateFormat.getPattern(); 084 } 085 } 086 087 private static final class FixedFormatter extends Formatter { 088 private final FixedDateFormat fixedDateFormat; 089 090 // below fields are only used in ThreadLocal caching mode 091 private final char[] cachedBuffer = new char[70]; // max length of formatted date-time in any format < 70 092 private int length = 0; 093 094 FixedFormatter(final FixedDateFormat fixedDateFormat) { 095 this.fixedDateFormat = fixedDateFormat; 096 } 097 098 @Override 099 String format(final Instant instant) { 100 return fixedDateFormat.formatInstant(instant); 101 } 102 103 @Override 104 void formatToBuffer(final Instant instant, final StringBuilder destination) { 105 final long epochSecond = instant.getEpochSecond(); 106 final int nanoOfSecond = instant.getNanoOfSecond(); 107 if (previousTime != epochSecond || nanos != nanoOfSecond) { 108 length = fixedDateFormat.formatInstant(instant, cachedBuffer, 0); 109 previousTime = epochSecond; 110 nanos = nanoOfSecond; 111 } 112 destination.append(cachedBuffer, 0, length); 113 } 114 115 @Override 116 public String toPattern() { 117 return fixedDateFormat.getFormat(); 118 } 119 } 120 121 private static final class UnixFormatter extends Formatter { 122 123 @Override 124 String format(final Instant instant) { 125 return Long.toString(instant.getEpochSecond()); 126 } 127 128 @Override 129 void formatToBuffer(final Instant instant, final StringBuilder destination) { 130 destination.append(instant.getEpochSecond()); // no need for caching 131 } 132 } 133 134 private static final class UnixMillisFormatter extends Formatter { 135 136 @Override 137 String format(final Instant instant) { 138 return Long.toString(instant.getEpochMillisecond()); 139 } 140 141 @Override 142 void formatToBuffer(final Instant instant, final StringBuilder destination) { 143 destination.append(instant.getEpochMillisecond()); // no need for caching 144 } 145 } 146 147 private final class CachedTime { 148 public long epochSecond; 149 public int nanoOfSecond; 150 public String formatted; 151 152 public CachedTime(final Instant instant) { 153 this.epochSecond = instant.getEpochSecond(); 154 this.nanoOfSecond = instant.getNanoOfSecond(); 155 this.formatted = formatter.format(instant); 156 } 157 } 158 159 /** 160 * UNIX formatter in seconds (standard). 161 */ 162 private static final String UNIX_FORMAT = "UNIX"; 163 164 /** 165 * UNIX formatter in milliseconds 166 */ 167 private static final String UNIX_MILLIS_FORMAT = "UNIX_MILLIS"; 168 169 private final String[] options; 170 private final ThreadLocal<MutableInstant> threadLocalMutableInstant = new ThreadLocal<>(); 171 private final ThreadLocal<Formatter> threadLocalFormatter = new ThreadLocal<>(); 172 private final AtomicReference<CachedTime> cachedTime; 173 private final Formatter formatter; 174 175 /** 176 * Private constructor. 177 * 178 * @param options options, may be null. 179 */ 180 private DatePatternConverter(final String[] options) { 181 super("Date", "date"); 182 this.options = options == null ? null : Arrays.copyOf(options, options.length); 183 this.formatter = createFormatter(options); 184 cachedTime = new AtomicReference<>(fromEpochMillis(System.currentTimeMillis())); 185 } 186 187 private CachedTime fromEpochMillis(final long epochMillis) { 188 final MutableInstant temp = new MutableInstant(); 189 temp.initFromEpochMilli(epochMillis, 0); 190 return new CachedTime(temp); 191 } 192 193 private Formatter createFormatter(final String[] options) { 194 final FixedDateFormat fixedDateFormat = FixedDateFormat.createIfSupported(options); 195 if (fixedDateFormat != null) { 196 return createFixedFormatter(fixedDateFormat); 197 } 198 return createNonFixedFormatter(options); 199 } 200 201 /** 202 * Obtains an instance of pattern converter. 203 * 204 * @param options options, may be null. 205 * @return instance of pattern converter. 206 */ 207 public static DatePatternConverter newInstance(final String[] options) { 208 return new DatePatternConverter(options); 209 } 210 211 private static Formatter createFixedFormatter(final FixedDateFormat fixedDateFormat) { 212 return new FixedFormatter(fixedDateFormat); 213 } 214 215 private static Formatter createNonFixedFormatter(final String[] options) { 216 // if we get here, options is a non-null array with at least one element (first of which non-null) 217 Objects.requireNonNull(options); 218 if (options.length == 0) { 219 throw new IllegalArgumentException("Options array must have at least one element"); 220 } 221 Objects.requireNonNull(options[0]); 222 final String patternOption = options[0]; 223 if (UNIX_FORMAT.equals(patternOption)) { 224 return new UnixFormatter(); 225 } 226 if (UNIX_MILLIS_FORMAT.equals(patternOption)) { 227 return new UnixMillisFormatter(); 228 } 229 // LOG4J2-1149: patternOption may be a name (if a time zone was specified) 230 final FixedDateFormat.FixedFormat fixedFormat = FixedDateFormat.FixedFormat.lookup(patternOption); 231 final String pattern = fixedFormat == null ? patternOption : fixedFormat.getPattern(); 232 233 // if the option list contains a TZ option, then set it. 234 TimeZone tz = null; 235 if (options.length > 1 && options[1] != null) { 236 tz = TimeZone.getTimeZone(options[1]); 237 } 238 239 try { 240 final FastDateFormat tempFormat = FastDateFormat.getInstance(pattern, tz); 241 return new PatternFormatter(tempFormat); 242 } catch (final IllegalArgumentException e) { 243 LOGGER.warn("Could not instantiate FastDateFormat with pattern " + pattern, e); 244 245 // default to the DEFAULT format 246 return createFixedFormatter(FixedDateFormat.create(FixedFormat.DEFAULT, tz)); 247 } 248 } 249 250 /** 251 * Appends formatted date to string buffer. 252 * 253 * @param date date 254 * @param toAppendTo buffer to which formatted date is appended. 255 */ 256 public void format(final Date date, final StringBuilder toAppendTo) { 257 format(date.getTime(), toAppendTo); 258 } 259 260 /** 261 * {@inheritDoc} 262 */ 263 @Override 264 public void format(final LogEvent event, final StringBuilder output) { 265 format(event.getInstant(), output); 266 } 267 268 public void format(final long epochMilli, final StringBuilder output) { 269 final MutableInstant instant = getMutableInstant(); 270 instant.initFromEpochMilli(epochMilli, 0); 271 format(instant, output); 272 } 273 274 private MutableInstant getMutableInstant() { 275 if (Constants.ENABLE_THREADLOCALS) { 276 MutableInstant result = threadLocalMutableInstant.get(); 277 if (result == null) { 278 result = new MutableInstant(); 279 threadLocalMutableInstant.set(result); 280 } 281 return result; 282 } 283 return new MutableInstant(); 284 } 285 286 public void format(final Instant instant, final StringBuilder output) { 287 if (Constants.ENABLE_THREADLOCALS) { 288 formatWithoutAllocation(instant, output); 289 } else { 290 formatWithoutThreadLocals(instant, output); 291 } 292 } 293 294 private void formatWithoutAllocation(final Instant instant, final StringBuilder output) { 295 getThreadLocalFormatter().formatToBuffer(instant, output); 296 } 297 298 private Formatter getThreadLocalFormatter() { 299 Formatter result = threadLocalFormatter.get(); 300 if (result == null) { 301 result = createFormatter(options); 302 threadLocalFormatter.set(result); 303 } 304 return result; 305 } 306 307 private void formatWithoutThreadLocals(final Instant instant, final StringBuilder output) { 308 CachedTime cached = cachedTime.get(); 309 if (instant.getEpochSecond() != cached.epochSecond || instant.getNanoOfSecond() != cached.nanoOfSecond) { 310 final CachedTime newTime = new CachedTime(instant); 311 if (cachedTime.compareAndSet(cached, newTime)) { 312 cached = newTime; 313 } else { 314 cached = cachedTime.get(); 315 } 316 } 317 output.append(cached.formatted); 318 } 319 320 /** 321 * {@inheritDoc} 322 */ 323 @Override 324 public void format(final Object obj, final StringBuilder output) { 325 if (obj instanceof Date) { 326 format((Date) obj, output); 327 } 328 super.format(obj, output); 329 } 330 331 @Override 332 public void format(final StringBuilder toAppendTo, final Object... objects) { 333 for (final Object obj : objects) { 334 if (obj instanceof Date) { 335 format(obj, toAppendTo); 336 break; 337 } 338 } 339 } 340 341 /** 342 * Gets the pattern string describing this date format. 343 * 344 * @return the pattern string describing this date format. 345 */ 346 public String getPattern() { 347 return formatter.toPattern(); 348 } 349 350}