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