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