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.text.SimpleDateFormat;
020import java.util.Date;
021import java.util.TimeZone;
022
023import org.apache.logging.log4j.core.LogEvent;
024import org.apache.logging.log4j.core.config.plugins.Plugin;
025
026/**
027 * Convert and format the event's date in a StringBuilder.
028 */
029@Plugin(name = "DatePatternConverter", category = PatternConverter.CATEGORY)
030@ConverterKeys({ "d", "date" })
031public final class DatePatternConverter extends LogEventPatternConverter implements ArrayPatternConverter {
032
033    private abstract static class Formatter {
034        abstract String format(long time);
035
036        public String toPattern() {
037            return null;
038        }
039    }
040
041    private static class PatternFormatter extends Formatter {
042        private final SimpleDateFormat simpleDateFormat;
043
044        PatternFormatter(final SimpleDateFormat simpleDateFormat) {
045            this.simpleDateFormat = simpleDateFormat;
046        }
047
048        @Override
049        String format(final long time) {
050            return simpleDateFormat.format(Long.valueOf(time));
051        }
052
053        @Override
054        public String toPattern() {
055            return simpleDateFormat.toPattern();
056        }
057    }
058
059    private static class UnixFormatter extends Formatter {
060
061        @Override
062        String format(final long time) {
063            return Long.toString(time / 1000);
064        }
065
066    }
067
068    private static class UnixMillisFormatter extends Formatter {
069
070        @Override
071        String format(final long time) {
072            return Long.toString(time);
073        }
074
075    }
076
077    /**
078     * ABSOLUTE string literal.
079     */
080    private static final String ABSOLUTE_FORMAT = "ABSOLUTE";
081
082    /**
083     * SimpleTimePattern for ABSOLUTE.
084     */
085    private static final String ABSOLUTE_TIME_PATTERN = "HH:mm:ss,SSS";
086
087    /**
088     * COMPACT string literal.
089     */
090    private static final String COMPACT_FORMAT = "COMPACT";
091
092    /**
093     * SimpleTimePattern for COMPACT.
094     */
095    private static final String COMPACT_PATTERN = "yyyyMMddHHmmssSSS";
096
097    /**
098     * DATE string literal.
099     */
100    private static final String DATE_AND_TIME_FORMAT = "DATE";
101
102    /**
103     * SimpleTimePattern for DATE.
104     */
105    private static final String DATE_AND_TIME_PATTERN = "dd MMM yyyy HH:mm:ss,SSS";
106
107    /**
108     * DEFAULT string literal.
109     */
110    private static final String DEFAULT_FORMAT = "DEFAULT";
111
112    /**
113     * SimpleTimePattern for DEFAULT.
114     */
115    // package private for unit tests
116    static final String DEFAULT_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS";
117
118    /**
119     * ISO8601_BASIC string literal.
120     */
121    private static final String ISO8601_BASIC_FORMAT = "ISO8601_BASIC";
122
123    /**
124     * SimpleTimePattern for ISO8601_BASIC.
125     */
126    private static final String ISO8601_BASIC_PATTERN = "yyyyMMdd'T'HHmmss,SSS";
127
128    /**
129     * ISO8601 string literal.
130     */
131    // package private for unit tests
132    static final String ISO8601_FORMAT = "ISO8601";
133
134    /**
135     * SimpleTimePattern for ISO8601.
136     */
137    // package private for unit tests
138    static final String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss,SSS";
139
140    /**
141     * UNIX formatter in seconds (standard).
142     */
143    private static final String UNIX_FORMAT = "UNIX";
144
145    /**
146     * UNIX formatter in milliseconds
147     */
148    private static final String UNIX_MILLIS_FORMAT = "UNIX_MILLIS";
149
150    /**
151     * Obtains an instance of pattern converter.
152     *
153     * @param options
154     *            options, may be null.
155     * @return instance of pattern converter.
156     */
157    public static DatePatternConverter newInstance(final String[] options) {
158        return new DatePatternConverter(options);
159    }
160
161    /**
162     * Date format.
163     */
164    private String cachedDateString;
165
166    private final Formatter formatter;
167
168    private long lastTimestamp;
169
170    /**
171     * Private constructor.
172     *
173     * @param options
174     *            options, may be null.
175     */
176    private DatePatternConverter(final String[] options) {
177        super("Date", "date");
178
179        // null patternOption is OK.
180        final String patternOption = options != null && options.length > 0 ? options[0] : null;
181
182        String pattern = null;
183        Formatter tempFormatter = null;
184
185        if (patternOption == null || patternOption.equalsIgnoreCase(DEFAULT_FORMAT)) {
186            pattern = DEFAULT_PATTERN;
187        } else if (patternOption.equalsIgnoreCase(ISO8601_FORMAT)) {
188            pattern = ISO8601_PATTERN;
189        } else if (patternOption.equalsIgnoreCase(ISO8601_BASIC_FORMAT)) {
190            pattern = ISO8601_BASIC_PATTERN;
191        } else if (patternOption.equalsIgnoreCase(ABSOLUTE_FORMAT)) {
192            pattern = ABSOLUTE_TIME_PATTERN;
193        } else if (patternOption.equalsIgnoreCase(DATE_AND_TIME_FORMAT)) {
194            pattern = DATE_AND_TIME_PATTERN;
195        } else if (patternOption.equalsIgnoreCase(COMPACT_FORMAT)) {
196            pattern = COMPACT_PATTERN;
197        } else if (patternOption.equalsIgnoreCase(UNIX_FORMAT)) {
198            tempFormatter = new UnixFormatter();
199        } else if (patternOption.equalsIgnoreCase(UNIX_MILLIS_FORMAT)) {
200            tempFormatter = new UnixMillisFormatter();
201        } else {
202            pattern = patternOption;
203        }
204
205        if (pattern != null) {
206            SimpleDateFormat tempFormat;
207
208            try {
209                tempFormat = new SimpleDateFormat(pattern);
210            } catch (final IllegalArgumentException e) {
211                LOGGER.warn("Could not instantiate SimpleDateFormat with pattern " + patternOption, e);
212
213                // default to the DEFAULT format
214                tempFormat = new SimpleDateFormat(DEFAULT_PATTERN);
215            }
216
217            // if the option list contains a TZ option, then set it.
218            if (options != null && options.length > 1) {
219                final TimeZone tz = TimeZone.getTimeZone(options[1]);
220                tempFormat.setTimeZone(tz);
221            }
222            tempFormatter = new PatternFormatter(tempFormat);
223        }
224        formatter = tempFormatter;
225    }
226
227    /**
228     * Append formatted date to string buffer.
229     *
230     * @param date
231     *            date
232     * @param toAppendTo
233     *            buffer to which formatted date is appended.
234     */
235    public void format(final Date date, final StringBuilder toAppendTo) {
236        synchronized (this) {
237            toAppendTo.append(formatter.format(date.getTime()));
238        }
239    }
240
241    /**
242     * {@inheritDoc}
243     */
244    @Override
245    public void format(final LogEvent event, final StringBuilder output) {
246        final long timestamp = event.getTimeMillis();
247
248        synchronized (this) {
249            if (timestamp != lastTimestamp) {
250                lastTimestamp = timestamp;
251                cachedDateString = formatter.format(timestamp);
252            }
253        }
254        output.append(cachedDateString);
255    }
256
257    /**
258     * {@inheritDoc}
259     */
260    @Override
261    public void format(final Object obj, final StringBuilder output) {
262        if (obj instanceof Date) {
263            format((Date) obj, output);
264        }
265        super.format(obj, output);
266    }
267
268    @Override
269    public void format(final StringBuilder toAppendTo, final Object... objects) {
270        for (final Object obj : objects) {
271            if (obj instanceof Date) {
272                format(obj, toAppendTo);
273                break;
274            }
275        }
276    }
277
278    /**
279     * Gets the pattern string describing this date format.
280     *
281     * @return the pattern string describing this date format.
282     */
283    public String getPattern() {
284        return formatter.toPattern();
285    }
286
287}