1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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;
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
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
91 private final char[] cachedBuffer = new char[64];
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());
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());
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
161
162 private static final String UNIX_FORMAT = "UNIX";
163
164
165
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
177
178
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(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
203
204
205
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
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
230 final FixedDateFormat.FixedFormat fixedFormat = FixedDateFormat.FixedFormat.lookup(patternOption);
231 final String pattern = fixedFormat == null ? patternOption : fixedFormat.getPattern();
232
233
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
246 return createFixedFormatter(FixedDateFormat.create(FixedFormat.DEFAULT, tz));
247 }
248 }
249
250
251
252
253
254
255
256 public void format(final Date date, final StringBuilder toAppendTo) {
257 format(date.getTime(), toAppendTo);
258 }
259
260
261
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 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 } else {
283 return new MutableInstant();
284 }
285 }
286
287 public void format(final Instant instant, final StringBuilder output) {
288 if (Constants.ENABLE_THREADLOCALS) {
289 formatWithoutAllocation(instant, output);
290 } else {
291 formatWithoutThreadLocals(instant, output);
292 }
293 }
294
295 private void formatWithoutAllocation(final Instant instant, final StringBuilder output) {
296 getThreadLocalFormatter().formatToBuffer(instant, output);
297 }
298
299 private Formatter getThreadLocalFormatter() {
300 Formatter result = threadLocalFormatter.get();
301 if (result == null) {
302 result = createFormatter(options);
303 threadLocalFormatter.set(result);
304 }
305 return result;
306 }
307
308 private void formatWithoutThreadLocals(final Instant instant, final StringBuilder output) {
309 CachedTime cached = cachedTime.get();
310 if (instant.getEpochSecond() != cached.epochSecond || instant.getNanoOfSecond() != cached.nanoOfSecond) {
311 final CachedTime newTime = new CachedTime(instant);
312 if (cachedTime.compareAndSet(cached, newTime)) {
313 cached = newTime;
314 } else {
315 cached = cachedTime.get();
316 }
317 }
318 output.append(cached.formatted);
319 }
320
321
322
323
324 @Override
325 public void format(final Object obj, final StringBuilder output) {
326 if (obj instanceof Date) {
327 format((Date) obj, output);
328 }
329 super.format(obj, output);
330 }
331
332 @Override
333 public void format(final StringBuilder toAppendTo, final Object... objects) {
334 for (final Object obj : objects) {
335 if (obj instanceof Date) {
336 format(obj, toAppendTo);
337 break;
338 }
339 }
340 }
341
342
343
344
345
346
347 public String getPattern() {
348 return formatter.toPattern();
349 }
350
351 }