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