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.util.datetime.FastDateFormat;
29  import org.apache.logging.log4j.core.util.datetime.FixedDateFormat;
30  import org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat;
31  
32  /**
33   * Converts and formats the event's date in a StringBuilder.
34   */
35  @Plugin(name = "DatePatternConverter", category = PatternConverter.CATEGORY)
36  @ConverterKeys({"d", "date"})
37  public final class DatePatternConverter extends LogEventPatternConverter implements ArrayPatternConverter {
38  
39      private abstract static class Formatter {
40          long previousTime; // for ThreadLocal caching mode
41  
42          abstract String format(long timeMillis);
43  
44          abstract void formatToBuffer(long timeMillis, StringBuilder destination);
45  
46          public String toPattern() {
47              return null;
48          }
49      }
50  
51      private static final class PatternFormatter extends Formatter {
52          private final FastDateFormat fastDateFormat;
53  
54          // this field is only used in ThreadLocal caching mode
55          private final StringBuilder cachedBuffer = new StringBuilder(64);
56  
57          PatternFormatter(final FastDateFormat fastDateFormat) {
58              this.fastDateFormat = fastDateFormat;
59          }
60  
61          @Override
62          String format(final long timeMillis) {
63              return fastDateFormat.format(timeMillis);
64          }
65  
66          @Override
67          void formatToBuffer(final long timeMillis, final StringBuilder destination) {
68              if (previousTime != timeMillis) {
69                  cachedBuffer.setLength(0);
70                  fastDateFormat.format(timeMillis, cachedBuffer);
71              }
72              destination.append(cachedBuffer);
73          }
74  
75          @Override
76          public String toPattern() {
77              return fastDateFormat.toPattern();
78          }
79      }
80  
81      private static final class FixedFormatter extends Formatter {
82          private final FixedDateFormat fixedDateFormat;
83  
84          // below fields are only used in ThreadLocal caching mode
85          private final char[] cachedBuffer = new char[64]; // max length of formatted date-time in any format < 64
86          private int length = 0;
87  
88          FixedFormatter(final FixedDateFormat fixedDateFormat) {
89              this.fixedDateFormat = fixedDateFormat;
90          }
91  
92          @Override
93          String format(final long timeMillis) {
94              return fixedDateFormat.format(timeMillis);
95          }
96  
97          @Override
98          void formatToBuffer(final long timeMillis, final StringBuilder destination) {
99              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 }