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.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
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;
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
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
85 private final char[] cachedBuffer = new char[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);
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);
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
149
150 private static final String UNIX_FORMAT = "UNIX";
151
152
153
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
164
165
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
184
185
186
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
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
211 final FixedDateFormat.FixedFormat fixedFormat = FixedDateFormat.FixedFormat.lookup(patternOption);
212 final String pattern = fixedFormat == null ? patternOption : fixedFormat.getPattern();
213
214
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
227 return createFixedFormatter(FixedDateFormat.create(FixedFormat.DEFAULT));
228 }
229 }
230
231
232
233
234
235
236
237 public void format(final Date date, final StringBuilder toAppendTo) {
238 format(date.getTime(), toAppendTo);
239 }
240
241
242
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
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
307
308
309
310 public String getPattern() {
311 return formatter.toPattern();
312 }
313
314 }