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.layout;
018
019import java.nio.charset.Charset;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.logging.log4j.core.Layout;
025import org.apache.logging.log4j.core.LogEvent;
026import org.apache.logging.log4j.core.config.Configuration;
027import org.apache.logging.log4j.core.config.DefaultConfiguration;
028import org.apache.logging.log4j.core.config.Node;
029import org.apache.logging.log4j.core.config.plugins.Plugin;
030import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
031import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
032import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
033import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
034import org.apache.logging.log4j.core.config.plugins.PluginElement;
035import org.apache.logging.log4j.core.config.plugins.PluginFactory;
036import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
037import org.apache.logging.log4j.core.pattern.PatternFormatter;
038import org.apache.logging.log4j.core.pattern.PatternParser;
039import org.apache.logging.log4j.core.pattern.RegexReplacement;
040
041/**
042 * A flexible layout configurable with pattern string.
043 * <p>
044 * The goal of this class is to {@link org.apache.logging.log4j.core.Layout#toByteArray format} a {@link LogEvent} and
045 * return the results. The format of the result depends on the <em>conversion pattern</em>.
046 * </p>
047 * <p>
048 * The conversion pattern is closely related to the conversion pattern of the printf function in C. A conversion pattern
049 * is composed of literal text and format control expressions called <em>conversion specifiers</em>.
050 * </p>
051 * <p>
052 * See the Log4j Manual for details on the supported pattern converters.
053 * </p>
054 */
055@Plugin(name = "PatternLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
056public final class PatternLayout extends AbstractStringLayout {
057
058    /**
059     * Default pattern string for log output. Currently set to the
060     * string <b>"%m%n"</b> which just prints the application supplied
061     * message.
062     */
063    public static final String DEFAULT_CONVERSION_PATTERN = "%m%n";
064
065    /**
066     * A conversion pattern equivalent to the TTCCCLayout.
067     * Current value is <b>%r [%t] %p %c %x - %m%n</b>.
068     */
069    public static final String TTCC_CONVERSION_PATTERN =
070        "%r [%t] %p %c %x - %m%n";
071
072    /**
073     * A simple pattern.
074     * Current value is <b>%d [%t] %p %c - %m%n</b>.
075     */
076    public static final String SIMPLE_CONVERSION_PATTERN =
077        "%d [%t] %p %c - %m%n";
078
079    /** Key to identify pattern converters. */
080    public static final String KEY = "Converter";
081
082    private static final long serialVersionUID = 1L;
083
084    /**
085     * Initial converter for pattern.
086     */
087    private final PatternFormatter[] formatters;
088
089    /**
090     * Conversion pattern.
091     */
092    private final String conversionPattern;
093
094    private final PatternSelector patternSelector;
095
096    private final Serializer serializer;
097
098
099    /**
100     * The current Configuration.
101     */
102    private final Configuration config;
103
104    private final RegexReplacement replace;
105
106    private final boolean alwaysWriteExceptions;
107
108    private final boolean noConsoleNoAnsi;
109
110    /**
111     * Constructs a EnhancedPatternLayout using the supplied conversion pattern.
112     *
113     * @param config The Configuration.
114     * @param replace The regular expression to match.
115     * @param pattern conversion pattern.
116     * @param patternSelector The PatternSelector.
117     * @param charset The character set.
118     * @param alwaysWriteExceptions Whether or not exceptions should always be handled in this pattern (if {@code true},
119     *                         exceptions will be written even if the pattern does not specify so).
120     * @param noConsoleNoAnsi
121     *            If {@code "true"} (default) and {@link System#console()} is null, do not output ANSI escape codes
122     * @param header
123     */
124    private PatternLayout(final Configuration config, final RegexReplacement replace, final String pattern,
125                          final PatternSelector patternSelector, final Charset charset,
126                          final boolean alwaysWriteExceptions, final boolean noConsoleNoAnsi,
127                          final String header, final String footer) {
128        super(charset, toBytes(header, charset), toBytes(footer, charset));
129        this.replace = replace;
130        this.conversionPattern = pattern;
131        this.patternSelector = patternSelector;
132        this.config = config;
133        this.alwaysWriteExceptions = alwaysWriteExceptions;
134        this.noConsoleNoAnsi = noConsoleNoAnsi;
135        if (patternSelector == null) {
136            serializer = new PatternSerializer();
137            final PatternParser parser = createPatternParser(config);
138            try {
139                List<PatternFormatter> list = parser.parse(pattern == null ? DEFAULT_CONVERSION_PATTERN : pattern,
140                        this.alwaysWriteExceptions, this.noConsoleNoAnsi);
141                this.formatters = list.toArray(new PatternFormatter[0]);
142            } catch (RuntimeException ex) {
143                throw new IllegalArgumentException("Cannot parse pattern '" + pattern + "'", ex);
144            }
145        } else {
146            this.formatters = null;
147            serializer = new PatternSelectorSerializer();
148        }
149    }
150
151    private byte[] strSubstitutorReplace(final byte... b) {
152        if (b != null && config != null) {
153            return getBytes(config.getStrSubstitutor().replace(new String(b, getCharset())));
154        }
155        return b;
156    }
157
158    @Override
159    public byte[] getHeader() {
160        return strSubstitutorReplace(super.getHeader());
161    }
162
163    @Override
164    public byte[] getFooter() {
165        return strSubstitutorReplace(super.getFooter());
166    }
167
168    /**
169     * Gets the conversion pattern.
170     *
171     * @return the conversion pattern.
172     */
173    public String getConversionPattern() {
174        return conversionPattern;
175    }
176
177    /**
178     * Gets this PatternLayout's content format. Specified by:
179     * <ul>
180     * <li>Key: "structured" Value: "false"</li>
181     * <li>Key: "formatType" Value: "conversion" (format uses the keywords supported by OptionConverter)</li>
182     * <li>Key: "format" Value: provided "conversionPattern" param</li>
183     * </ul>
184     * 
185     * @return Map of content format keys supporting PatternLayout
186     */
187    @Override
188    public Map<String, String> getContentFormat()
189    {
190        final Map<String, String> result = new HashMap<>();
191        result.put("structured", "false");
192        result.put("formatType", "conversion");
193        result.put("format", conversionPattern);
194        return result;
195    }
196
197    /**
198     * Formats a logging event to a writer.
199     *
200     * @param event logging event to be formatted.
201     * @return The event formatted as a String.
202     */
203    @Override
204    public String toSerializable(final LogEvent event) {
205        return serializer.toSerializable(event);
206    }
207
208    /**
209     * Creates a PatternParser.
210     * @param config The Configuration.
211     * @return The PatternParser.
212     */
213    public static PatternParser createPatternParser(final Configuration config) {
214        if (config == null) {
215            return new PatternParser(config, KEY, LogEventPatternConverter.class);
216        }
217        PatternParser parser = config.getComponent(KEY);
218        if (parser == null) {
219            parser = new PatternParser(config, KEY, LogEventPatternConverter.class);
220            config.addComponent(KEY, parser);
221            parser = (PatternParser) config.getComponent(KEY);
222        }
223        return parser;
224    }
225
226    @Override
227    public String toString() {
228        return patternSelector == null ? conversionPattern : patternSelector.toString();
229    }
230
231    /**
232     * Creates a pattern layout.
233     *
234     * @param pattern
235     *        The pattern. If not specified, defaults to DEFAULT_CONVERSION_PATTERN.
236     * @param config
237     *        The Configuration. Some Converters require access to the Interpolator.
238     * @param replace
239     *        A Regex replacement String.
240     * @param charset
241     *        The character set. The platform default is used if not specified.
242     * @param alwaysWriteExceptions
243     *        If {@code "true"} (default) exceptions are always written even if the pattern contains no exception tokens.
244     * @param noConsoleNoAnsi
245     *        If {@code "true"} (default is false) and {@link System#console()} is null, do not output ANSI escape codes
246     * @param header
247     *        The footer to place at the top of the document, once.
248     * @param footer
249     *        The footer to place at the bottom of the document, once.
250     * @return The PatternLayout.
251     */
252    @PluginFactory
253    public static PatternLayout createLayout(
254            @PluginAttribute(value = "pattern", defaultString = DEFAULT_CONVERSION_PATTERN) final String pattern,
255            @PluginElement("PatternSelector") final PatternSelector patternSelector,
256            @PluginConfiguration final Configuration config,
257            @PluginElement("Replace") final RegexReplacement replace,
258            // LOG4J2-783 use platform default by default, so do not specify defaultString for charset
259            @PluginAttribute(value = "charset") final Charset charset,
260            @PluginAttribute(value = "alwaysWriteExceptions", defaultBoolean = true) final boolean alwaysWriteExceptions,
261            @PluginAttribute(value = "noConsoleNoAnsi", defaultBoolean = false) final boolean noConsoleNoAnsi,
262            @PluginAttribute("header") final String header,
263            @PluginAttribute("footer") final String footer) {
264        return newBuilder()
265            .withPattern(pattern)
266            .withPatternSelector(patternSelector)
267            .withConfiguration(config)
268            .withRegexReplacement(replace)
269            .withCharset(charset)
270            .withAlwaysWriteExceptions(alwaysWriteExceptions)
271            .withNoConsoleNoAnsi(noConsoleNoAnsi)
272            .withHeader(header)
273            .withFooter(footer)
274            .build();
275    }
276
277
278    private interface Serializer {
279
280        String toSerializable(final LogEvent event);
281    }
282
283    private class PatternSerializer implements Serializer {
284        @Override
285        public String toSerializable(final LogEvent event) {
286            final StringBuilder buf = getStringBuilder();
287            final int len = formatters.length;
288            for (int i = 0; i < len; i++) {
289                formatters[i].format(event, buf);
290            }
291            String str = buf.toString();
292            if (replace != null) {
293                str = replace.format(str);
294            }
295            return str;
296        }
297    }
298
299    private class PatternSelectorSerializer implements Serializer {
300        @Override
301        public String toSerializable(final LogEvent event) {
302            final StringBuilder buf = getStringBuilder();
303            PatternFormatter[] formatters = patternSelector.getFormatters(event);
304            final int len = formatters.length;
305            for (int i = 0; i < len; i++) {
306                formatters[i].format(event, buf);
307            }
308            String str = buf.toString();
309            if (replace != null) {
310                str = replace.format(str);
311            }
312            return str;
313        }
314    }
315
316    /**
317     * Creates a PatternLayout using the default options. These options include using UTF-8, the default conversion
318     * pattern, exceptions being written, and with ANSI escape codes.
319     *
320     * @return the PatternLayout.
321     * @see #DEFAULT_CONVERSION_PATTERN Default conversion pattern
322     */
323    public static PatternLayout createDefaultLayout() {
324        return newBuilder().build();
325    }
326
327    /**
328     * Creates a builder for a custom PatternLayout.
329     * @return a PatternLayout builder.
330     */
331    @PluginBuilderFactory
332    public static Builder newBuilder() {
333        return new Builder();
334    }
335
336    /**
337     * Custom PatternLayout builder. Use the {@link PatternLayout#newBuilder() builder factory method} to create this.
338     */
339    public static class Builder implements org.apache.logging.log4j.core.util.Builder<PatternLayout> {
340
341        // FIXME: it seems rather redundant to repeat default values (same goes for field names)
342        // perhaps introduce a @PluginBuilderAttribute that has no values of its own and uses reflection?
343
344        @PluginBuilderAttribute
345        private String pattern = PatternLayout.DEFAULT_CONVERSION_PATTERN;
346
347        @PluginElement("PatternSelector")
348        private PatternSelector patternSelector = null;
349
350        @PluginConfiguration
351        private Configuration configuration = null;
352
353        @PluginElement("Replace")
354        private RegexReplacement regexReplacement = null;
355
356        // LOG4J2-783 use platform default by default
357        @PluginBuilderAttribute
358        private Charset charset = Charset.defaultCharset();
359
360        @PluginBuilderAttribute
361        private boolean alwaysWriteExceptions = true;
362
363        @PluginBuilderAttribute
364        private boolean noConsoleNoAnsi = false;
365
366        @PluginBuilderAttribute
367        private String header = null;
368
369        @PluginBuilderAttribute
370        private String footer = null;
371
372        private Builder() {
373        }
374
375        // TODO: move javadocs from PluginFactory to here
376
377        public Builder withPattern(final String pattern) {
378            this.pattern = pattern;
379            return this;
380        }
381
382        public Builder withPatternSelector(final PatternSelector patternSelector) {
383            this.patternSelector = patternSelector;
384            return this;
385        }
386
387
388        public Builder withConfiguration(final Configuration configuration) {
389            this.configuration = configuration;
390            return this;
391        }
392
393        public Builder withRegexReplacement(final RegexReplacement regexReplacement) {
394            this.regexReplacement = regexReplacement;
395            return this;
396        }
397
398        public Builder withCharset(final Charset charset) {
399            // LOG4J2-783 if null, use platform default by default
400            if (charset != null) {
401                this.charset = charset;
402            }
403            return this;
404        }
405
406        public Builder withAlwaysWriteExceptions(final boolean alwaysWriteExceptions) {
407            this.alwaysWriteExceptions = alwaysWriteExceptions;
408            return this;
409        }
410
411        public Builder withNoConsoleNoAnsi(final boolean noConsoleNoAnsi) {
412            this.noConsoleNoAnsi = noConsoleNoAnsi;
413            return this;
414        }
415
416        public Builder withHeader(final String header) {
417            this.header = header;
418            return this;
419        }
420
421        public Builder withFooter(final String footer) {
422            this.footer = footer;
423            return this;
424        }
425
426        @Override
427        public PatternLayout build() {
428            // fall back to DefaultConfiguration
429            if (configuration == null) {
430                configuration = new DefaultConfiguration();
431            }
432            return new PatternLayout(configuration, regexReplacement, pattern, patternSelector, charset,
433                alwaysWriteExceptions, noConsoleNoAnsi, header, footer);
434        }
435    }
436}