001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.core.pattern;
018
019import java.util.Arrays;
020import java.util.Date;
021import java.util.Objects;
022import java.util.TimeZone;
023import java.util.concurrent.atomic.AtomicReference;
024
025import org.apache.logging.log4j.core.LogEvent;
026import org.apache.logging.log4j.core.config.plugins.Plugin;
027import org.apache.logging.log4j.core.util.Constants;
028import org.apache.logging.log4j.core.time.Instant;
029import org.apache.logging.log4j.core.time.MutableInstant;
030import org.apache.logging.log4j.core.util.datetime.FastDateFormat;
031import org.apache.logging.log4j.core.util.datetime.FixedDateFormat;
032import org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat;
033import org.apache.logging.log4j.util.PerformanceSensitive;
034
035/**
036 * Converts and formats the event's date in a StringBuilder.
037 */
038@Plugin(name = "DatePatternConverter", category = PatternConverter.CATEGORY)
039@ConverterKeys({"d", "date"})
040@PerformanceSensitive("allocation")
041public final class DatePatternConverter extends LogEventPatternConverter implements ArrayPatternConverter {
042
043    private abstract static class Formatter {
044        long previousTime; // for ThreadLocal caching mode
045        int nanos;
046
047        abstract String format(final Instant instant);
048
049        abstract void formatToBuffer(final Instant instant, StringBuilder destination);
050
051        public String toPattern() {
052            return null;
053        }
054    }
055
056    private static final class PatternFormatter extends Formatter {
057        private final FastDateFormat fastDateFormat;
058
059        // this field is only used in ThreadLocal caching mode
060        private final StringBuilder cachedBuffer = new StringBuilder(64);
061
062        PatternFormatter(final FastDateFormat fastDateFormat) {
063            this.fastDateFormat = fastDateFormat;
064        }
065
066        @Override
067        String format(final Instant instant) {
068            return fastDateFormat.format(instant.getEpochMillisecond());
069        }
070
071        @Override
072        void formatToBuffer(final Instant instant, final StringBuilder destination) {
073            final long timeMillis = instant.getEpochMillisecond();
074            if (previousTime != timeMillis) {
075                cachedBuffer.setLength(0);
076                fastDateFormat.format(timeMillis, cachedBuffer);
077            }
078            destination.append(cachedBuffer);
079        }
080
081        @Override
082        public String toPattern() {
083            return fastDateFormat.getPattern();
084        }
085    }
086
087    private static final class FixedFormatter extends Formatter {
088        private final FixedDateFormat fixedDateFormat;
089
090        // below fields are only used in ThreadLocal caching mode
091        private final char[] cachedBuffer = new char[70]; // max length of formatted date-time in any format < 70
092        private int length = 0;
093
094        FixedFormatter(final FixedDateFormat fixedDateFormat) {
095            this.fixedDateFormat = fixedDateFormat;
096        }
097
098        @Override
099        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}