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.Strings;
42  
43  /**
44   * A flexible layout configurable with pattern string.
45   * <p>
46   * The goal of this class is to {@link org.apache.logging.log4j.core.Layout#toByteArray format} a {@link LogEvent} and
47   * return the results. The format of the result depends on the <em>conversion pattern</em>.
48   * </p>
49   * <p>
50   * The conversion pattern is closely related to the conversion pattern of the printf function in C. A conversion pattern
51   * is composed of literal text and format control expressions called <em>conversion specifiers</em>.
52   * </p>
53   * <p>
54   * See the Log4j Manual for details on the supported pattern converters.
55   * </p>
56   */
57  @Plugin(name = "PatternLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
58  public final class PatternLayout extends AbstractStringLayout {
59  
60      /**
61       * Default pattern string for log output. Currently set to the string <b>"%m%n"</b> which just prints the
62       * application supplied message.
63       */
64      public static final String DEFAULT_CONVERSION_PATTERN = "%m%n";
65  
66      /**
67       * A conversion pattern equivalent to the TTCCLayout. Current value is <b>%r [%t] %p %c %notEmpty{%x }- %m%n</b>.
68       */
69      public static final String TTCC_CONVERSION_PATTERN = "%r [%t] %p %c %notEmpty{%x }- %m%n";
70  
71      /**
72       * A simple pattern. Current value is <b>%d [%t] %p %c - %m%n</b>.
73       */
74      public static final String SIMPLE_CONVERSION_PATTERN = "%d [%t] %p %c - %m%n";
75  
76      /** Key to identify pattern converters. */
77      public static final String KEY = "Converter";
78  
79      /**
80       * Conversion pattern.
81       */
82      private final String conversionPattern;
83      private final PatternSelector patternSelector;
84      private final Serializer eventSerializer;
85  
86      /**
87       * Constructs a PatternLayout using the supplied conversion pattern.
88       *
89       * @param config The Configuration.
90       * @param replace The regular expression to match.
91       * @param eventPattern conversion pattern.
92       * @param patternSelector The PatternSelector.
93       * @param charset The character set.
94       * @param alwaysWriteExceptions Whether or not exceptions should always be handled in this pattern (if {@code true},
95       *                         exceptions will be written even if the pattern does not specify so).
96       * @param disableAnsi
97       *            If {@code "true"}, do not output ANSI escape codes
98       * @param noConsoleNoAnsi
99       *            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 }