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