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