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