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