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.Date;
020import java.util.Objects;
021import java.util.TimeZone;
022import java.util.concurrent.atomic.AtomicReference;
023
024import org.apache.logging.log4j.core.LogEvent;
025import org.apache.logging.log4j.core.config.plugins.Plugin;
026import org.apache.logging.log4j.core.util.datetime.FastDateFormat;
027import org.apache.logging.log4j.core.util.datetime.FixedDateFormat;
028import org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat;
029
030/**
031 * Converts and formats the event's date in a StringBuilder.
032 */
033@Plugin(name = "DatePatternConverter", category = PatternConverter.CATEGORY)
034@ConverterKeys({"d", "date"})
035public final class DatePatternConverter extends LogEventPatternConverter implements ArrayPatternConverter {
036
037    private abstract static class Formatter {
038        abstract String format(long timeMillis);
039
040        public String toPattern() {
041            return null;
042        }
043    }
044
045    private static final class PatternFormatter extends Formatter {
046        private final FastDateFormat fastDateFormat;
047
048        PatternFormatter(final FastDateFormat fastDateFormat) {
049            this.fastDateFormat = fastDateFormat;
050        }
051
052        @Override
053        String format(final long timeMillis) {
054            return fastDateFormat.format(timeMillis);
055        }
056
057        @Override
058        public String toPattern() {
059            return fastDateFormat.toPattern();
060        }
061    }
062
063    private static final class FixedFormatter extends Formatter {
064        private final FixedDateFormat fixedDateFormat;
065
066        FixedFormatter(final FixedDateFormat fixedDateFormat) {
067            this.fixedDateFormat = fixedDateFormat;
068        }
069
070        @Override
071        String format(final long timeMillis) {
072            return fixedDateFormat.format(timeMillis);
073        }
074
075        @Override
076        public String toPattern() {
077            return fixedDateFormat.getFormat();
078        }
079    }
080
081    private static final class UnixFormatter extends Formatter {
082
083        @Override
084        String format(final long timeMillis) {
085            return Long.toString(timeMillis / 1000);
086        }
087    }
088
089    private static final class UnixMillisFormatter extends Formatter {
090
091        @Override
092        String format(final long timeMillis) {
093            return Long.toString(timeMillis);
094        }
095    }
096
097    private final class CachedTime {
098        public long timestampMillis;
099        public String formatted;
100
101        public CachedTime(final long timestampMillis) {
102            this.timestampMillis = timestampMillis;
103            this.formatted = formatter.format(this.timestampMillis);
104        }
105    }
106
107    /**
108     * UNIX formatter in seconds (standard).
109     */
110    private static final String UNIX_FORMAT = "UNIX";
111
112    /**
113     * UNIX formatter in milliseconds
114     */
115    private static final String UNIX_MILLIS_FORMAT = "UNIX_MILLIS";
116
117    private final AtomicReference<CachedTime> cachedTime;
118    private final Formatter formatter;
119
120    /**
121     * Private constructor.
122     *
123     * @param options options, may be null.
124     */
125    private DatePatternConverter(final String[] options) {
126        super("Date", "date");
127
128        final FixedDateFormat fixedDateFormat = FixedDateFormat.createIfSupported(options);
129        if (fixedDateFormat != null) {
130            formatter = createFormatter(fixedDateFormat);
131        } else {
132            formatter = createFormatter(options);
133        }
134        cachedTime = new AtomicReference<>(new CachedTime(System.currentTimeMillis()));
135    }
136
137    /**
138     * Obtains an instance of pattern converter.
139     *
140     * @param options options, may be null.
141     * @return instance of pattern converter.
142     */
143    public static DatePatternConverter newInstance(final String[] options) {
144        return new DatePatternConverter(options);
145    }
146
147    private static Formatter createFormatter(final FixedDateFormat fixedDateFormat) {
148        return new FixedFormatter(fixedDateFormat);
149    }
150
151    private static Formatter createFormatter(final String[] options) {
152        // if we get here, options is a non-null array with at least one element (first of which non-null)
153        Objects.requireNonNull(options);
154        if (options.length == 0) {
155            throw new IllegalArgumentException("options array must have at least one element");
156        }
157        Objects.requireNonNull(options[0]);
158        final String patternOption = options[0];
159        if (UNIX_FORMAT.equals(patternOption)) {
160            return new UnixFormatter();
161        }
162        if (UNIX_MILLIS_FORMAT.equals(patternOption)) {
163            return new UnixMillisFormatter();
164        }
165        // LOG4J2-1149: patternOption may be a name (if a time zone was specified)
166        final FixedDateFormat.FixedFormat fixedFormat = FixedDateFormat.FixedFormat.lookup(patternOption);
167        final String pattern = fixedFormat == null ? patternOption : fixedFormat.getPattern();
168
169        // if the option list contains a TZ option, then set it.
170        TimeZone tz = null;
171        if (options.length > 1 && options[1] != null) {
172            tz = TimeZone.getTimeZone(options[1]);
173        }
174
175        try {
176            final FastDateFormat tempFormat = FastDateFormat.getInstance(pattern, tz);
177            return new PatternFormatter(tempFormat);
178        } catch (final IllegalArgumentException e) {
179            LOGGER.warn("Could not instantiate FastDateFormat with pattern " + pattern, e);
180
181            // default to the DEFAULT format
182            return createFormatter(FixedDateFormat.create(FixedFormat.DEFAULT));
183        }
184    }
185
186    /**
187     * Appends formatted date to string buffer.
188     *
189     * @param date date
190     * @param toAppendTo buffer to which formatted date is appended.
191     */
192    public void format(final Date date, final StringBuilder toAppendTo) {
193        format(date.getTime(), toAppendTo);
194    }
195
196    /**
197     * {@inheritDoc}
198     */
199    @Override
200    public void format(final LogEvent event, final StringBuilder output) {
201        format(event.getTimeMillis(), output);
202    }
203
204    public void format(final long timestampMillis, final StringBuilder output) {
205        CachedTime cached = cachedTime.get();
206        if (timestampMillis != cached.timestampMillis) {
207            final CachedTime newTime = new CachedTime(timestampMillis);
208            if (cachedTime.compareAndSet(cached, newTime)) {
209                cached = newTime;
210            } else {
211                cached = cachedTime.get();
212            }
213        }
214        output.append(cached.formatted);
215    }
216
217    /**
218     * {@inheritDoc}
219     */
220    @Override
221    public void format(final Object obj, final StringBuilder output) {
222        if (obj instanceof Date) {
223            format((Date) obj, output);
224        }
225        super.format(obj, output);
226    }
227
228    @Override
229    public void format(final StringBuilder toAppendTo, final Object... objects) {
230        for (final Object obj : objects) {
231            if (obj instanceof Date) {
232                format(obj, toAppendTo);
233                break;
234            }
235        }
236    }
237
238    /**
239     * Gets the pattern string describing this date format.
240     *
241     * @return the pattern string describing this date format.
242     */
243    public String getPattern() {
244        return formatter.toPattern();
245    }
246
247}