View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.pattern;
18  
19  import java.util.Arrays;
20  import java.util.Date;
21  import java.util.Objects;
22  import java.util.TimeZone;
23  import java.util.concurrent.atomic.AtomicReference;
24  
25  import org.apache.logging.log4j.core.LogEvent;
26  import org.apache.logging.log4j.core.config.plugins.Plugin;
27  import org.apache.logging.log4j.core.util.Constants;
28  import org.apache.logging.log4j.core.time.Instant;
29  import org.apache.logging.log4j.core.time.MutableInstant;
30  import org.apache.logging.log4j.core.util.datetime.FastDateFormat;
31  import org.apache.logging.log4j.core.util.datetime.FixedDateFormat;
32  import org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat;
33  import org.apache.logging.log4j.util.PerformanceSensitive;
34  
35  /**
36   * Converts and formats the event's date in a StringBuilder.
37   */
38  @Plugin(name = "DatePatternConverter", category = PatternConverter.CATEGORY)
39  @ConverterKeys({"d", "date"})
40  @PerformanceSensitive("allocation")
41  public final class DatePatternConverter extends LogEventPatternConverter implements ArrayPatternConverter {
42  
43      private abstract static class Formatter {
44          long previousTime; // for ThreadLocal caching mode
45          int nanos;
46  
47          abstract String format(final Instant instant);
48  
49          abstract void formatToBuffer(final Instant instant, StringBuilder destination);
50  
51          public String toPattern() {
52              return null;
53          }
54      }
55  
56      private static final class PatternFormatter extends Formatter {
57          private final FastDateFormat fastDateFormat;
58  
59          // this field is only used in ThreadLocal caching mode
60          private final StringBuilder cachedBuffer = new StringBuilder(64);
61  
62          PatternFormatter(final FastDateFormat fastDateFormat) {
63              this.fastDateFormat = fastDateFormat;
64          }
65  
66          @Override
67          String format(final Instant instant) {
68              return fastDateFormat.format(instant.getEpochMillisecond());
69          }
70  
71          @Override
72          void formatToBuffer(final Instant instant, final StringBuilder destination) {
73              final long timeMillis = instant.getEpochMillisecond();
74              if (previousTime != timeMillis) {
75                  cachedBuffer.setLength(0);
76                  fastDateFormat.format(timeMillis, cachedBuffer);
77              }
78              destination.append(cachedBuffer);
79          }
80  
81          @Override
82          public String toPattern() {
83              return fastDateFormat.getPattern();
84          }
85      }
86  
87      private static final class FixedFormatter extends Formatter {
88          private final FixedDateFormat fixedDateFormat;
89  
90          // below fields are only used in ThreadLocal caching mode
91          private final char[] cachedBuffer = new char[70]; // max length of formatted date-time in any format < 70
92          private int length = 0;
93  
94          FixedFormatter(final FixedDateFormat fixedDateFormat) {
95              this.fixedDateFormat = fixedDateFormat;
96          }
97  
98          @Override
99          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 }