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