View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.layout;
18  
19  import java.nio.charset.Charset;
20  import java.util.Arrays;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  
25  import org.apache.logging.log4j.core.Layout;
26  import org.apache.logging.log4j.core.LogEvent;
27  import org.apache.logging.log4j.core.config.Configuration;
28  import org.apache.logging.log4j.core.config.DefaultConfiguration;
29  import org.apache.logging.log4j.core.config.Node;
30  import org.apache.logging.log4j.core.config.plugins.Plugin;
31  import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
32  import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
33  import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
34  import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
35  import org.apache.logging.log4j.core.config.plugins.PluginElement;
36  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
37  import org.apache.logging.log4j.core.impl.LocationAware;
38  import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
39  import org.apache.logging.log4j.core.pattern.PatternFormatter;
40  import org.apache.logging.log4j.core.pattern.PatternParser;
41  import org.apache.logging.log4j.core.pattern.RegexReplacement;
42  import org.apache.logging.log4j.util.PropertiesUtil;
43  import org.apache.logging.log4j.util.Strings;
44  
45  /**
46   * A flexible layout configurable with pattern string.
47   * <p>
48   * The goal of this class is to {@link org.apache.logging.log4j.core.Layout#toByteArray format} a {@link LogEvent} and
49   * return the results. The format of the result depends on the <em>conversion pattern</em>.
50   * </p>
51   * <p>
52   * The conversion pattern is closely related to the conversion pattern of the printf function in C. A conversion pattern
53   * is composed of literal text and format control expressions called <em>conversion specifiers</em>.
54   * </p>
55   * <p>
56   * See the Log4j Manual for details on the supported pattern converters.
57   * </p>
58   */
59  @Plugin(name = "PatternLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
60  public final class PatternLayout extends AbstractStringLayout {
61  
62      /**
63       * Default pattern string for log output. Currently set to the string <b>"%m%n"</b> which just prints the
64       * application supplied message.
65       */
66      public static final String DEFAULT_CONVERSION_PATTERN = "%m%n";
67  
68      /**
69       * A conversion pattern equivalent to the TTCCLayout. Current value is <b>%r [%t] %p %c %notEmpty{%x }- %m%n</b>.
70       */
71      public static final String TTCC_CONVERSION_PATTERN = "%r [%t] %p %c %notEmpty{%x }- %m%n";
72  
73      /**
74       * A simple pattern. Current value is <b>%d [%t] %p %c - %m%n</b>.
75       */
76      public static final String SIMPLE_CONVERSION_PATTERN = "%d [%t] %p %c - %m%n";
77  
78      /** Key to identify pattern converters. */
79      public static final String KEY = "Converter";
80  
81      /**
82       * Conversion pattern.
83       */
84      private final String conversionPattern;
85      private final PatternSelector patternSelector;
86      private final Serializer eventSerializer;
87  
88      /**
89       * Constructs a PatternLayout using the supplied conversion pattern.
90       *
91       * @param config The Configuration.
92       * @param replace The regular expression to match.
93       * @param eventPattern conversion pattern.
94       * @param patternSelector The PatternSelector.
95       * @param charset The character set.
96       * @param alwaysWriteExceptions Whether or not exceptions should always be handled in this pattern (if {@code true},
97       *                         exceptions will be written even if the pattern does not specify so).
98       * @param disableAnsi
99       *            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 }