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 TTCCCLayout. Current value is <b>%r [%t] %p %c %x - %m%n</b>.
68       */
69      public static final String TTCC_CONVERSION_PATTERN = "%r [%t] %p %c %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 EnhancedPatternLayout 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 noConsoleNoAnsi
97       *            If {@code "true"} (default) and {@link System#console()} is null, do not output ANSI escape codes
98       * @param headerPattern header conversion pattern.
99       * @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 }