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.util.datetime.FastDateFormat;
029import org.apache.logging.log4j.core.util.datetime.FixedDateFormat;
030import org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat;
031
032/**
033 * Converts and formats the event's date in a StringBuilder.
034 */
035@Plugin(name = "DatePatternConverter", category = PatternConverter.CATEGORY)
036@ConverterKeys({"d", "date"})
037public final class DatePatternConverter extends LogEventPatternConverter implements ArrayPatternConverter {
038
039    private abstract static class Formatter {
040        long previousTime; // for ThreadLocal caching mode
041
042        abstract String format(long timeMillis);
043
044        abstract void formatToBuffer(long timeMillis, StringBuilder destination);
045
046        public String toPattern() {
047            return null;
048        }
049    }
050
051    private static final class PatternFormatter extends Formatter {
052        private final FastDateFormat fastDateFormat;
053
054        // this field is only used in ThreadLocal caching mode
055        private final StringBuilder cachedBuffer = new StringBuilder(64);
056
057        PatternFormatter(final FastDateFormat fastDateFormat) {
058            this.fastDateFormat = fastDateFormat;
059        }
060
061        @Override
062        String format(final long timeMillis) {
063            return fastDateFormat.format(timeMillis);
064        }
065
066        @Override
067        void formatToBuffer(final long timeMillis, final StringBuilder destination) {
068            if (previousTime != timeMillis) {
069                cachedBuffer.setLength(0);
070                fastDateFormat.format(timeMillis, cachedBuffer);
071            }
072            destination.append(cachedBuffer);
073        }
074
075        @Override
076        public String toPattern() {
077            return fastDateFormat.toPattern();
078        }
079    }
080
081    private static final class FixedFormatter extends Formatter {
082        private final FixedDateFormat fixedDateFormat;
083
084        // below fields are only used in ThreadLocal caching mode
085        private final char[] cachedBuffer = new char[64]; // max length of formatted date-time in any format < 64
086        private int length = 0;
087
088        FixedFormatter(final FixedDateFormat fixedDateFormat) {
089            this.fixedDateFormat = fixedDateFormat;
090        }
091
092        @Override
093        String format(final long timeMillis) {
094            return fixedDateFormat.format(timeMillis);
095        }
096
097        @Override
098        void formatToBuffer(final long timeMillis, final StringBuilder destination) {
099            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}