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