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        trimToMaxSize(text);
184    }
185
186    /**
187     * Creates a text representation of the specified log event
188     * and writes it into the specified StringBuilder.
189     * <p>
190     * Implementations are free to return a new StringBuilder if they can
191     * detect in advance that the specified StringBuilder is too small.
192     */
193    private StringBuilder toText(final Serializer2 serializer, final LogEvent event,
194            final StringBuilder destination) {
195        return serializer.toSerializable(event, destination);
196    }
197
198    /**
199     * Creates a PatternParser.
200     * @param config The Configuration.
201     * @return The PatternParser.
202     */
203    public static PatternParser createPatternParser(final Configuration config) {
204        if (config == null) {
205            return new PatternParser(config, KEY, LogEventPatternConverter.class);
206        }
207        PatternParser parser = config.getComponent(KEY);
208        if (parser == null) {
209            parser = new PatternParser(config, KEY, LogEventPatternConverter.class);
210            config.addComponent(KEY, parser);
211            parser = config.getComponent(KEY);
212        }
213        return parser;
214    }
215
216    @Override
217    public String toString() {
218        return patternSelector == null ? conversionPattern : patternSelector.toString();
219    }
220
221    /**
222     * Creates a pattern layout.
223     *
224     * @param pattern
225     *        The pattern. If not specified, defaults to DEFAULT_CONVERSION_PATTERN.
226     * @param patternSelector
227     *        Allows different patterns to be used based on some selection criteria.
228     * @param config
229     *        The Configuration. Some Converters require access to the Interpolator.
230     * @param replace
231     *        A Regex replacement String.
232     * @param charset
233     *        The character set. The platform default is used if not specified.
234     * @param alwaysWriteExceptions
235     *        If {@code "true"} (default) exceptions are always written even if the pattern contains no exception tokens.
236     * @param noConsoleNoAnsi
237     *        If {@code "true"} (default is false) and {@link System#console()} is null, do not output ANSI escape codes
238     * @param headerPattern
239     *        The footer to place at the top of the document, once.
240     * @param footerPattern
241     *        The footer to place at the bottom of the document, once.
242     * @return The PatternLayout.
243     */
244    @PluginFactory
245    public static PatternLayout createLayout(
246            @PluginAttribute(value = "pattern", defaultString = DEFAULT_CONVERSION_PATTERN) final String pattern,
247            @PluginElement("PatternSelector") final PatternSelector patternSelector,
248            @PluginConfiguration final Configuration config,
249            @PluginElement("Replace") final RegexReplacement replace,
250            // LOG4J2-783 use platform default by default, so do not specify defaultString for charset
251            @PluginAttribute(value = "charset") final Charset charset,
252            @PluginAttribute(value = "alwaysWriteExceptions", defaultBoolean = true) final boolean alwaysWriteExceptions,
253            @PluginAttribute(value = "noConsoleNoAnsi", defaultBoolean = false) final boolean noConsoleNoAnsi,
254            @PluginAttribute("header") final String headerPattern,
255            @PluginAttribute("footer") final String footerPattern) {
256        return newBuilder()
257            .withPattern(pattern)
258            .withPatternSelector(patternSelector)
259            .withConfiguration(config)
260            .withRegexReplacement(replace)
261            .withCharset(charset)
262            .withAlwaysWriteExceptions(alwaysWriteExceptions)
263            .withNoConsoleNoAnsi(noConsoleNoAnsi)
264            .withHeader(headerPattern)
265            .withFooter(footerPattern)
266            .build();
267    }
268
269    private static class PatternSerializer implements Serializer, Serializer2 {
270
271        private final PatternFormatter[] formatters;
272        private final RegexReplacement replace;
273
274        private PatternSerializer(final PatternFormatter[] formatters, final RegexReplacement replace) {
275            super();
276            this.formatters = formatters;
277            this.replace = replace;
278        }
279
280        @Override
281        public String toSerializable(final LogEvent event) {
282            final StringBuilder sb = getStringBuilder();
283            try {
284                return toSerializable(event, sb).toString();
285            } finally {
286                trimToMaxSize(sb);
287            }
288        }
289
290        @Override
291        public StringBuilder toSerializable(final LogEvent event, final StringBuilder buffer) {
292            final int len = formatters.length;
293            for (int i = 0; i < len; i++) {
294                formatters[i].format(event, buffer);
295            }
296            if (replace != null) { // creates temporary objects
297                String str = buffer.toString();
298                str = replace.format(str);
299                buffer.setLength(0);
300                buffer.append(str);
301            }
302            return buffer;
303        }
304
305        @Override
306        public String toString() {
307            final StringBuilder builder = new StringBuilder();
308            builder.append(super.toString());
309            builder.append("[formatters=");
310            builder.append(Arrays.toString(formatters));
311            builder.append(", replace=");
312            builder.append(replace);
313            builder.append("]");
314            return builder.toString();
315        }
316    }
317
318    private static class PatternSelectorSerializer implements Serializer, Serializer2 {
319
320        private final PatternSelector patternSelector;
321        private final RegexReplacement replace;
322
323        private PatternSelectorSerializer(final PatternSelector patternSelector, final RegexReplacement replace) {
324            super();
325            this.patternSelector = patternSelector;
326            this.replace = replace;
327        }
328
329        @Override
330        public String toSerializable(final LogEvent event) {
331            final StringBuilder sb = getStringBuilder();
332            try {
333                return toSerializable(event, sb).toString();
334            } finally {
335                trimToMaxSize(sb);
336            }
337        }
338
339        @Override
340        public StringBuilder toSerializable(final LogEvent event, final StringBuilder buffer) {
341            final PatternFormatter[] formatters = patternSelector.getFormatters(event);
342            final int len = formatters.length;
343            for (int i = 0; i < len; i++) {
344                formatters[i].format(event, buffer);
345            }
346            if (replace != null) { // creates temporary objects
347                String str = buffer.toString();
348                str = replace.format(str);
349                buffer.setLength(0);
350                buffer.append(str);
351            }
352            return buffer;
353        }
354
355        @Override
356        public String toString() {
357            final StringBuilder builder = new StringBuilder();
358            builder.append(super.toString());
359            builder.append("[patternSelector=");
360            builder.append(patternSelector);
361            builder.append(", replace=");
362            builder.append(replace);
363            builder.append("]");
364            return builder.toString();
365        }
366    }
367
368    /**
369     * Creates a PatternLayout using the default options. These options include using UTF-8, the default conversion
370     * pattern, exceptions being written, and with ANSI escape codes.
371     *
372     * @return the PatternLayout.
373     * @see #DEFAULT_CONVERSION_PATTERN Default conversion pattern
374     */
375    public static PatternLayout createDefaultLayout() {
376        return newBuilder().build();
377    }
378
379    /**
380     * Creates a PatternLayout using the default options and the given configuration. These options include using UTF-8,
381     * the default conversion pattern, exceptions being written, and with ANSI escape codes.
382     *
383     * @param configuration The Configuration.
384     *
385     * @return the PatternLayout.
386     * @see #DEFAULT_CONVERSION_PATTERN Default conversion pattern
387     */
388    public static PatternLayout createDefaultLayout(final Configuration configuration) {
389        return newBuilder().withConfiguration(configuration).build();
390    }
391
392    /**
393     * Creates a builder for a custom PatternLayout.
394     *
395     * @return a PatternLayout builder.
396     */
397    @PluginBuilderFactory
398    public static Builder newBuilder() {
399        return new Builder();
400    }
401
402    /**
403     * Custom PatternLayout builder. Use the {@link PatternLayout#newBuilder() builder factory method} to create this.
404     */
405    public static class Builder implements org.apache.logging.log4j.core.util.Builder<PatternLayout> {
406
407        @PluginBuilderAttribute
408        private String pattern = PatternLayout.DEFAULT_CONVERSION_PATTERN;
409
410        @PluginElement("PatternSelector")
411        private PatternSelector patternSelector = null;
412
413        @PluginConfiguration
414        private Configuration configuration = null;
415
416        @PluginElement("Replace")
417        private RegexReplacement regexReplacement = null;
418
419        // LOG4J2-783 use platform default by default
420        @PluginBuilderAttribute
421        private Charset charset = Charset.defaultCharset();
422
423        @PluginBuilderAttribute
424        private boolean alwaysWriteExceptions = true;
425
426        @PluginBuilderAttribute
427        private boolean noConsoleNoAnsi = false;
428
429        @PluginBuilderAttribute
430        private String header = null;
431
432        @PluginBuilderAttribute
433        private String footer = null;
434
435        private Builder() {
436        }
437
438        // TODO: move javadocs from PluginFactory to here
439
440        public Builder withPattern(final String pattern) {
441            this.pattern = pattern;
442            return this;
443        }
444
445        public Builder withPatternSelector(final PatternSelector patternSelector) {
446            this.patternSelector = patternSelector;
447            return this;
448        }
449
450        public Builder withConfiguration(final Configuration configuration) {
451            this.configuration = configuration;
452            return this;
453        }
454
455        public Builder withRegexReplacement(final RegexReplacement regexReplacement) {
456            this.regexReplacement = regexReplacement;
457            return this;
458        }
459
460        public Builder withCharset(final Charset charset) {
461            // LOG4J2-783 if null, use platform default by default
462            if (charset != null) {
463                this.charset = charset;
464            }
465            return this;
466        }
467
468        public Builder withAlwaysWriteExceptions(final boolean alwaysWriteExceptions) {
469            this.alwaysWriteExceptions = alwaysWriteExceptions;
470            return this;
471        }
472
473        public Builder withNoConsoleNoAnsi(final boolean noConsoleNoAnsi) {
474            this.noConsoleNoAnsi = noConsoleNoAnsi;
475            return this;
476        }
477
478        public Builder withHeader(final String header) {
479            this.header = header;
480            return this;
481        }
482
483        public Builder withFooter(final String footer) {
484            this.footer = footer;
485            return this;
486        }
487
488        @Override
489        public PatternLayout build() {
490            // fall back to DefaultConfiguration
491            if (configuration == null) {
492                configuration = new DefaultConfiguration();
493            }
494            return new PatternLayout(configuration, regexReplacement, pattern, patternSelector, charset,
495                alwaysWriteExceptions, noConsoleNoAnsi, header, footer);
496        }
497    }
498}