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.PropertiesUtil;
042import org.apache.logging.log4j.util.Strings;
043
044/**
045 * A flexible layout configurable with pattern string.
046 * <p>
047 * The goal of this class is to {@link org.apache.logging.log4j.core.Layout#toByteArray format} a {@link LogEvent} and
048 * return the results. The format of the result depends on the <em>conversion pattern</em>.
049 * </p>
050 * <p>
051 * The conversion pattern is closely related to the conversion pattern of the printf function in C. A conversion pattern
052 * is composed of literal text and format control expressions called <em>conversion specifiers</em>.
053 * </p>
054 * <p>
055 * See the Log4j Manual for details on the supported pattern converters.
056 * </p>
057 */
058@Plugin(name = "PatternLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
059public final class PatternLayout extends AbstractStringLayout {
060
061    /**
062     * Default pattern string for log output. Currently set to the string <b>"%m%n"</b> which just prints the
063     * application supplied message.
064     */
065    public static final String DEFAULT_CONVERSION_PATTERN = "%m%n";
066
067    /**
068     * A conversion pattern equivalent to the TTCCLayout. Current value is <b>%r [%t] %p %c %notEmpty{%x }- %m%n</b>.
069     */
070    public static final String TTCC_CONVERSION_PATTERN = "%r [%t] %p %c %notEmpty{%x }- %m%n";
071
072    /**
073     * A simple pattern. Current value is <b>%d [%t] %p %c - %m%n</b>.
074     */
075    public static final String SIMPLE_CONVERSION_PATTERN = "%d [%t] %p %c - %m%n";
076
077    /** Key to identify pattern converters. */
078    public static final String KEY = "Converter";
079
080    /**
081     * Conversion pattern.
082     */
083    private final String conversionPattern;
084    private final PatternSelector patternSelector;
085    private final Serializer eventSerializer;
086
087    /**
088     * Constructs a PatternLayout using the supplied conversion pattern.
089     *
090     * @param config The Configuration.
091     * @param replace The regular expression to match.
092     * @param eventPattern conversion pattern.
093     * @param patternSelector The PatternSelector.
094     * @param charset The character set.
095     * @param alwaysWriteExceptions Whether or not exceptions should always be handled in this pattern (if {@code true},
096     *                         exceptions will be written even if the pattern does not specify so).
097     * @param disableAnsi
098     *            If {@code "true"}, do not output ANSI escape codes
099     * @param noConsoleNoAnsi
100     *            If {@code "true"} (default) and {@link System#console()} is null, do not output ANSI escape codes
101     * @param headerPattern header conversion pattern.
102     * @param footerPattern footer conversion pattern.
103     */
104    private PatternLayout(final Configuration config, final RegexReplacement replace, final String eventPattern,
105            final PatternSelector patternSelector, final Charset charset, final boolean alwaysWriteExceptions,
106            final boolean disableAnsi, final boolean noConsoleNoAnsi, final String headerPattern,
107            final String footerPattern) {
108        super(config, charset,
109                newSerializerBuilder()
110                        .setConfiguration(config)
111                        .setReplace(replace)
112                        .setPatternSelector(patternSelector)
113                        .setAlwaysWriteExceptions(alwaysWriteExceptions)
114                        .setDisableAnsi(disableAnsi)
115                        .setNoConsoleNoAnsi(noConsoleNoAnsi)
116                        .setPattern(headerPattern)
117                        .build(),
118                newSerializerBuilder()
119                        .setConfiguration(config)
120                        .setReplace(replace)
121                        .setPatternSelector(patternSelector)
122                        .setAlwaysWriteExceptions(alwaysWriteExceptions)
123                        .setDisableAnsi(disableAnsi)
124                        .setNoConsoleNoAnsi(noConsoleNoAnsi)
125                        .setPattern(footerPattern)
126                        .build());
127        this.conversionPattern = eventPattern;
128        this.patternSelector = patternSelector;
129        this.eventSerializer = newSerializerBuilder()
130                .setConfiguration(config)
131                .setReplace(replace)
132                .setPatternSelector(patternSelector)
133                .setAlwaysWriteExceptions(alwaysWriteExceptions)
134                .setDisableAnsi(disableAnsi)
135                .setNoConsoleNoAnsi(noConsoleNoAnsi)
136                .setPattern(eventPattern)
137                .setDefaultPattern(DEFAULT_CONVERSION_PATTERN)
138                .build();
139    }
140
141    public static SerializerBuilder newSerializerBuilder() {
142        return new SerializerBuilder();
143    }
144
145    /**
146     * Deprecated, use {@link #newSerializerBuilder()} instead.
147     *
148     * @param configuration
149     * @param replace
150     * @param pattern
151     * @param defaultPattern
152     * @param patternSelector
153     * @param alwaysWriteExceptions
154     * @param noConsoleNoAnsi
155     * @return a new Serializer.
156     * @deprecated Use {@link #newSerializerBuilder()} instead.
157     */
158    @Deprecated
159    public static Serializer createSerializer(final Configuration configuration, final RegexReplacement replace,
160            final String pattern, final String defaultPattern, final PatternSelector patternSelector,
161            final boolean alwaysWriteExceptions, final boolean noConsoleNoAnsi) {
162        final SerializerBuilder builder = newSerializerBuilder();
163        builder.setAlwaysWriteExceptions(alwaysWriteExceptions);
164        builder.setConfiguration(configuration);
165        builder.setDefaultPattern(defaultPattern);
166        builder.setNoConsoleNoAnsi(noConsoleNoAnsi);
167        builder.setPattern(pattern);
168        builder.setPatternSelector(patternSelector);
169        builder.setReplace(replace);
170        return builder.build();
171    }
172
173    /**
174     * Gets the conversion pattern.
175     *
176     * @return the conversion pattern.
177     */
178    public String getConversionPattern() {
179        return conversionPattern;
180    }
181
182    /**
183     * Gets this PatternLayout's content format. Specified by:
184     * <ul>
185     * <li>Key: "structured" Value: "false"</li>
186     * <li>Key: "formatType" Value: "conversion" (format uses the keywords supported by OptionConverter)</li>
187     * <li>Key: "format" Value: provided "conversionPattern" param</li>
188     * </ul>
189     *
190     * @return Map of content format keys supporting PatternLayout
191     */
192    @Override
193    public Map<String, String> getContentFormat() {
194        final Map<String, String> result = new HashMap<>();
195        result.put("structured", "false");
196        result.put("formatType", "conversion");
197        result.put("format", conversionPattern);
198        return result;
199    }
200
201    /**
202     * Formats a logging event to a writer.
203     *
204     * @param event logging event to be formatted.
205     * @return The event formatted as a String.
206     */
207    @Override
208    public String toSerializable(final LogEvent event) {
209        return eventSerializer.toSerializable(event);
210    }
211
212    @Override
213    public void encode(final LogEvent event, final ByteBufferDestination destination) {
214        if (!(eventSerializer instanceof Serializer2)) {
215            super.encode(event, destination);
216            return;
217        }
218        final StringBuilder text = toText((Serializer2) eventSerializer, event, getStringBuilder());
219        final Encoder<StringBuilder> encoder = getStringBuilderEncoder();
220        encoder.encode(text, destination);
221        trimToMaxSize(text);
222    }
223
224    /**
225     * Creates a text representation of the specified log event
226     * and writes it into the specified StringBuilder.
227     * <p>
228     * Implementations are free to return a new StringBuilder if they can
229     * detect in advance that the specified StringBuilder is too small.
230     */
231    private StringBuilder toText(final Serializer2 serializer, final LogEvent event,
232            final StringBuilder destination) {
233        return serializer.toSerializable(event, destination);
234    }
235
236    /**
237     * Creates a PatternParser.
238     * @param config The Configuration.
239     * @return The PatternParser.
240     */
241    public static PatternParser createPatternParser(final Configuration config) {
242        if (config == null) {
243            return new PatternParser(config, KEY, LogEventPatternConverter.class);
244        }
245        PatternParser parser = config.getComponent(KEY);
246        if (parser == null) {
247            parser = new PatternParser(config, KEY, LogEventPatternConverter.class);
248            config.addComponent(KEY, parser);
249            parser = config.getComponent(KEY);
250        }
251        return parser;
252    }
253
254    @Override
255    public String toString() {
256        return patternSelector == null ? conversionPattern : patternSelector.toString();
257    }
258
259    /**
260     * Creates a pattern layout.
261     *
262     * @param pattern
263     *        The pattern. If not specified, defaults to DEFAULT_CONVERSION_PATTERN.
264     * @param patternSelector
265     *        Allows different patterns to be used based on some selection criteria.
266     * @param config
267     *        The Configuration. Some Converters require access to the Interpolator.
268     * @param replace
269     *        A Regex replacement String.
270     * @param charset
271     *        The character set. The platform default is used if not specified.
272     * @param alwaysWriteExceptions
273     *        If {@code "true"} (default) exceptions are always written even if the pattern contains no exception tokens.
274     * @param noConsoleNoAnsi
275     *        If {@code "true"} (default is false) and {@link System#console()} is null, do not output ANSI escape codes
276     * @param headerPattern
277     *        The footer to place at the top of the document, once.
278     * @param footerPattern
279     *        The footer to place at the bottom of the document, once.
280     * @return The PatternLayout.
281     * @deprecated Use {@link #newBuilder()} instead. This will be private in a future version.
282     */
283    @PluginFactory
284    @Deprecated
285    public static PatternLayout createLayout(
286            @PluginAttribute(value = "pattern", defaultString = DEFAULT_CONVERSION_PATTERN) final String pattern,
287            @PluginElement("PatternSelector") final PatternSelector patternSelector,
288            @PluginConfiguration final Configuration config,
289            @PluginElement("Replace") final RegexReplacement replace,
290            // LOG4J2-783 use platform default by default, so do not specify defaultString for charset
291            @PluginAttribute(value = "charset") final Charset charset,
292            @PluginAttribute(value = "alwaysWriteExceptions", defaultBoolean = true) final boolean alwaysWriteExceptions,
293            @PluginAttribute(value = "noConsoleNoAnsi") final boolean noConsoleNoAnsi,
294            @PluginAttribute("header") final String headerPattern,
295            @PluginAttribute("footer") final String footerPattern) {
296        return newBuilder()
297            .withPattern(pattern)
298            .withPatternSelector(patternSelector)
299            .withConfiguration(config)
300            .withRegexReplacement(replace)
301            .withCharset(charset)
302            .withAlwaysWriteExceptions(alwaysWriteExceptions)
303            .withNoConsoleNoAnsi(noConsoleNoAnsi)
304            .withHeader(headerPattern)
305            .withFooter(footerPattern)
306            .build();
307    }
308
309    private static class PatternSerializer implements Serializer, Serializer2 {
310
311        private final PatternFormatter[] formatters;
312        private final RegexReplacement replace;
313
314        private PatternSerializer(final PatternFormatter[] formatters, final RegexReplacement replace) {
315            super();
316            this.formatters = formatters;
317            this.replace = replace;
318        }
319
320        @Override
321        public String toSerializable(final LogEvent event) {
322            final StringBuilder sb = getStringBuilder();
323            try {
324                return toSerializable(event, sb).toString();
325            } finally {
326                trimToMaxSize(sb);
327            }
328        }
329
330        @Override
331        public StringBuilder toSerializable(final LogEvent event, final StringBuilder buffer) {
332            final int len = formatters.length;
333            for (int i = 0; i < len; i++) {
334                formatters[i].format(event, buffer);
335            }
336            if (replace != null) { // creates temporary objects
337                String str = buffer.toString();
338                str = replace.format(str);
339                buffer.setLength(0);
340                buffer.append(str);
341            }
342            return buffer;
343        }
344
345        @Override
346        public String toString() {
347            final StringBuilder builder = new StringBuilder();
348            builder.append(super.toString());
349            builder.append("[formatters=");
350            builder.append(Arrays.toString(formatters));
351            builder.append(", replace=");
352            builder.append(replace);
353            builder.append("]");
354            return builder.toString();
355        }
356    }
357
358    public static class SerializerBuilder implements org.apache.logging.log4j.core.util.Builder<Serializer> {
359
360        private Configuration configuration;
361        private RegexReplacement replace;
362        private String pattern;
363        private String defaultPattern;
364        private PatternSelector patternSelector;
365        private boolean alwaysWriteExceptions;
366        private boolean disableAnsi;
367        private boolean noConsoleNoAnsi;
368
369        @Override
370        public Serializer build() {
371            if (Strings.isEmpty(pattern) && Strings.isEmpty(defaultPattern)) {
372                return null;
373            }
374            if (patternSelector == null) {
375                try {
376                    final PatternParser parser = createPatternParser(configuration);
377                    final List<PatternFormatter> list = parser.parse(pattern == null ? defaultPattern : pattern,
378                            alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi);
379                    final PatternFormatter[] formatters = list.toArray(new PatternFormatter[0]);
380                    return new PatternSerializer(formatters, replace);
381                } catch (final RuntimeException ex) {
382                    throw new IllegalArgumentException("Cannot parse pattern '" + pattern + "'", ex);
383                }
384            }
385            return new PatternSelectorSerializer(patternSelector, replace);
386        }
387
388        public SerializerBuilder setConfiguration(final Configuration configuration) {
389            this.configuration = configuration;
390            return this;
391        }
392
393        public SerializerBuilder setReplace(final RegexReplacement replace) {
394            this.replace = replace;
395            return this;
396        }
397
398        public SerializerBuilder setPattern(final String pattern) {
399            this.pattern = pattern;
400            return this;
401        }
402
403        public SerializerBuilder setDefaultPattern(final String defaultPattern) {
404            this.defaultPattern = defaultPattern;
405            return this;
406        }
407
408        public SerializerBuilder setPatternSelector(final PatternSelector patternSelector) {
409            this.patternSelector = patternSelector;
410            return this;
411        }
412
413        public SerializerBuilder setAlwaysWriteExceptions(final boolean alwaysWriteExceptions) {
414            this.alwaysWriteExceptions = alwaysWriteExceptions;
415            return this;
416        }
417
418        public SerializerBuilder setDisableAnsi(final boolean disableAnsi) {
419            this.disableAnsi = disableAnsi;
420            return this;
421        }
422
423        public SerializerBuilder setNoConsoleNoAnsi(final boolean noConsoleNoAnsi) {
424            this.noConsoleNoAnsi = noConsoleNoAnsi;
425            return this;
426        }
427
428    }
429
430    private static class PatternSelectorSerializer implements Serializer, Serializer2 {
431
432        private final PatternSelector patternSelector;
433        private final RegexReplacement replace;
434
435        private PatternSelectorSerializer(final PatternSelector patternSelector, final RegexReplacement replace) {
436            super();
437            this.patternSelector = patternSelector;
438            this.replace = replace;
439        }
440
441        @Override
442        public String toSerializable(final LogEvent event) {
443            final StringBuilder sb = getStringBuilder();
444            try {
445                return toSerializable(event, sb).toString();
446            } finally {
447                trimToMaxSize(sb);
448            }
449        }
450
451        @Override
452        public StringBuilder toSerializable(final LogEvent event, final StringBuilder buffer) {
453            final PatternFormatter[] formatters = patternSelector.getFormatters(event);
454            final int len = formatters.length;
455            for (int i = 0; i < len; i++) {
456                formatters[i].format(event, buffer);
457            }
458            if (replace != null) { // creates temporary objects
459                String str = buffer.toString();
460                str = replace.format(str);
461                buffer.setLength(0);
462                buffer.append(str);
463            }
464            return buffer;
465        }
466
467        @Override
468        public String toString() {
469            final StringBuilder builder = new StringBuilder();
470            builder.append(super.toString());
471            builder.append("[patternSelector=");
472            builder.append(patternSelector);
473            builder.append(", replace=");
474            builder.append(replace);
475            builder.append("]");
476            return builder.toString();
477        }
478    }
479
480    /**
481     * Creates a PatternLayout using the default options. These options include using UTF-8, the default conversion
482     * pattern, exceptions being written, and with ANSI escape codes.
483     *
484     * @return the PatternLayout.
485     * @see #DEFAULT_CONVERSION_PATTERN Default conversion pattern
486     */
487    public static PatternLayout createDefaultLayout() {
488        return newBuilder().build();
489    }
490
491    /**
492     * Creates a PatternLayout using the default options and the given configuration. These options include using UTF-8,
493     * the default conversion pattern, exceptions being written, and with ANSI escape codes.
494     *
495     * @param configuration The Configuration.
496     *
497     * @return the PatternLayout.
498     * @see #DEFAULT_CONVERSION_PATTERN Default conversion pattern
499     */
500    public static PatternLayout createDefaultLayout(final Configuration configuration) {
501        return newBuilder().withConfiguration(configuration).build();
502    }
503
504    /**
505     * Creates a builder for a custom PatternLayout.
506     *
507     * @return a PatternLayout builder.
508     */
509    @PluginBuilderFactory
510    public static Builder newBuilder() {
511        return new Builder();
512    }
513
514    /**
515     * Custom PatternLayout builder. Use the {@link PatternLayout#newBuilder() builder factory method} to create this.
516     */
517    public static class Builder implements org.apache.logging.log4j.core.util.Builder<PatternLayout> {
518
519        @PluginBuilderAttribute
520        private String pattern = PatternLayout.DEFAULT_CONVERSION_PATTERN;
521
522        @PluginElement("PatternSelector")
523        private PatternSelector patternSelector;
524
525        @PluginConfiguration
526        private Configuration configuration;
527
528        @PluginElement("Replace")
529        private RegexReplacement regexReplacement;
530
531        // LOG4J2-783 use platform default by default
532        @PluginBuilderAttribute
533        private Charset charset = Charset.defaultCharset();
534
535        @PluginBuilderAttribute
536        private boolean alwaysWriteExceptions = true;
537
538        @PluginBuilderAttribute
539        private boolean disableAnsi = !useAnsiEscapeCodes();
540
541        @PluginBuilderAttribute
542        private boolean noConsoleNoAnsi;
543
544        @PluginBuilderAttribute
545        private String header;
546
547        @PluginBuilderAttribute
548        private String footer;
549
550        private Builder() {
551        }
552
553        private boolean useAnsiEscapeCodes() {
554            PropertiesUtil propertiesUtil = PropertiesUtil.getProperties();
555            boolean isPlatformSupportsAnsi = !propertiesUtil.isOsWindows();
556            boolean isJansiRequested = !propertiesUtil.getBooleanProperty("log4j.skipJansi", true);
557            return isPlatformSupportsAnsi || isJansiRequested;
558        }
559
560        /**
561         * @param pattern
562         *        The pattern. If not specified, defaults to DEFAULT_CONVERSION_PATTERN.
563         */
564        public Builder withPattern(final String pattern) {
565            this.pattern = pattern;
566            return this;
567        }
568
569        /**
570         * @param patternSelector
571         *        Allows different patterns to be used based on some selection criteria.
572         */
573        public Builder withPatternSelector(final PatternSelector patternSelector) {
574            this.patternSelector = patternSelector;
575            return this;
576        }
577
578        /**
579         * @param configuration
580         *        The Configuration. Some Converters require access to the Interpolator.
581         */
582        public Builder withConfiguration(final Configuration configuration) {
583            this.configuration = configuration;
584            return this;
585        }
586
587        /**
588         * @param regexReplacement
589         *        A Regex replacement
590         */
591        public Builder withRegexReplacement(final RegexReplacement regexReplacement) {
592            this.regexReplacement = regexReplacement;
593            return this;
594        }
595
596        /**
597         * @param charset
598         *        The character set. The platform default is used if not specified.
599         */
600        public Builder withCharset(final Charset charset) {
601            // LOG4J2-783 if null, use platform default by default
602            if (charset != null) {
603                this.charset = charset;
604            }
605            return this;
606        }
607
608        /**
609         * @param alwaysWriteExceptions
610         *        If {@code "true"} (default) exceptions are always written even if the pattern contains no exception tokens.
611         */
612        public Builder withAlwaysWriteExceptions(final boolean alwaysWriteExceptions) {
613            this.alwaysWriteExceptions = alwaysWriteExceptions;
614            return this;
615        }
616
617        /**
618         * @param disableAnsi
619         *        If {@code "true"} (default is value of system property `log4j.skipJansi`, or `true` if undefined),
620         *        do not output ANSI escape codes
621         */
622        public Builder withDisableAnsi(final boolean disableAnsi) {
623            this.disableAnsi = disableAnsi;
624            return this;
625        }
626
627        /**
628         * @param noConsoleNoAnsi
629         *        If {@code "true"} (default is false) and {@link System#console()} is null, do not output ANSI escape codes
630         */
631        public Builder withNoConsoleNoAnsi(final boolean noConsoleNoAnsi) {
632            this.noConsoleNoAnsi = noConsoleNoAnsi;
633            return this;
634        }
635
636        /**
637         * @param header
638         *        The footer to place at the top of the document, once.
639         */
640        public Builder withHeader(final String header) {
641            this.header = header;
642            return this;
643        }
644
645        /**
646         * @param footer
647         *        The footer to place at the bottom of the document, once.
648         */
649        public Builder withFooter(final String footer) {
650            this.footer = footer;
651            return this;
652        }
653
654        @Override
655        public PatternLayout build() {
656            // fall back to DefaultConfiguration
657            if (configuration == null) {
658                configuration = new DefaultConfiguration();
659            }
660            return new PatternLayout(configuration, regexReplacement, pattern, patternSelector, charset,
661                alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi, header, footer);
662        }
663    }
664}