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