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.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.apache.logging.log4j.core.Layout;
25  import org.apache.logging.log4j.core.LogEvent;
26  import org.apache.logging.log4j.core.config.Configuration;
27  import org.apache.logging.log4j.core.config.DefaultConfiguration;
28  import org.apache.logging.log4j.core.config.Node;
29  import org.apache.logging.log4j.core.config.plugins.Plugin;
30  import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
31  import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
32  import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
33  import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
34  import org.apache.logging.log4j.core.config.plugins.PluginElement;
35  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
36  import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
37  import org.apache.logging.log4j.core.pattern.PatternFormatter;
38  import org.apache.logging.log4j.core.pattern.PatternParser;
39  import org.apache.logging.log4j.core.pattern.RegexReplacement;
40  import org.apache.logging.log4j.core.util.StringEncoder;
41  
42  /**
43   * A flexible layout configurable with pattern string.
44   * <p>
45   * The goal of this class is to {@link org.apache.logging.log4j.core.Layout#toByteArray format} a {@link LogEvent} and
46   * return the results. The format of the result depends on the <em>conversion pattern</em>.
47   * </p>
48   * <p>
49   * The conversion pattern is closely related to the conversion pattern of the printf function in C. A conversion pattern
50   * is composed of literal text and format control expressions called <em>conversion specifiers</em>.
51   * </p>
52   * <p>
53   * See the Log4j Manual for details on the supported pattern converters.
54   * </p>
55   */
56  @Plugin(name = "PatternLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
57  public final class PatternLayout extends AbstractStringLayout {
58  
59      /**
60       * Default pattern string for log output. Currently set to the
61       * string <b>"%m%n"</b> which just prints the application supplied
62       * message.
63       */
64      public static final String DEFAULT_CONVERSION_PATTERN = "%m%n";
65  
66      /**
67       * A conversion pattern equivalent to the TTCCCLayout.
68       * Current value is <b>%r [%t] %p %c %x - %m%n</b>.
69       */
70      public static final String TTCC_CONVERSION_PATTERN =
71          "%r [%t] %p %c %x - %m%n";
72  
73      /**
74       * A simple pattern.
75       * Current value is <b>%d [%t] %p %c - %m%n</b>.
76       */
77      public static final String SIMPLE_CONVERSION_PATTERN =
78          "%d [%t] %p %c - %m%n";
79  
80      /** Key to identify pattern converters. */
81      public static final String KEY = "Converter";
82  
83      private static final long serialVersionUID = 1L;
84  
85      /**
86       * Initial converter for pattern.
87       */
88      private final PatternFormatter[] formatters;
89  
90      /**
91       * Conversion pattern.
92       */
93      private final String conversionPattern;
94  
95      private final PatternSelector patternSelector;
96  
97      private final Serializer serializer;
98  
99  
100     /**
101      * The current Configuration.
102      */
103     private final Configuration config;
104 
105     private final RegexReplacement replace;
106 
107     private final boolean alwaysWriteExceptions;
108 
109     private final boolean noConsoleNoAnsi;
110 
111     /**
112      * Constructs a EnhancedPatternLayout using the supplied conversion pattern.
113      *
114      * @param config The Configuration.
115      * @param replace The regular expression to match.
116      * @param pattern conversion pattern.
117      * @param patternSelector The PatternSelector.
118      * @param charset The character set.
119      * @param alwaysWriteExceptions Whether or not exceptions should always be handled in this pattern (if {@code true},
120      *                         exceptions will be written even if the pattern does not specify so).
121      * @param noConsoleNoAnsi
122      *            If {@code "true"} (default) and {@link System#console()} is null, do not output ANSI escape codes
123      * @param header
124      */
125     private PatternLayout(final Configuration config, final RegexReplacement replace, final String pattern,
126                           final PatternSelector patternSelector, final Charset charset,
127                           final boolean alwaysWriteExceptions, final boolean noConsoleNoAnsi,
128                           final String header, final String footer) {
129         super(charset, StringEncoder.toBytes(header, charset), StringEncoder.toBytes(footer, charset));
130         this.replace = replace;
131         this.conversionPattern = pattern;
132         this.patternSelector = patternSelector;
133         this.config = config;
134         this.alwaysWriteExceptions = alwaysWriteExceptions;
135         this.noConsoleNoAnsi = noConsoleNoAnsi;
136         if (patternSelector == null) {
137             serializer = new PatternSerializer();
138             final PatternParser parser = createPatternParser(config);
139             try {
140                 List<PatternFormatter> list = parser.parse(pattern == null ? DEFAULT_CONVERSION_PATTERN : pattern,
141                         this.alwaysWriteExceptions, this.noConsoleNoAnsi);
142                 this.formatters = list.toArray(new PatternFormatter[0]);
143             } catch (RuntimeException ex) {
144                 throw new IllegalArgumentException("Cannot parse pattern '" + pattern + "'", ex);
145             }
146         } else {
147             this.formatters = null;
148             serializer = new PatternSelectorSerializer();
149         }
150     }
151 
152     private byte[] strSubstitutorReplace(final byte... b) {
153         if (b != null && config != null) {
154             return getBytes(config.getStrSubstitutor().replace(new String(b, getCharset())));
155         }
156         return b;
157     }
158 
159     @Override
160     public byte[] getHeader() {
161         return strSubstitutorReplace(super.getHeader());
162     }
163 
164     @Override
165     public byte[] getFooter() {
166         return strSubstitutorReplace(super.getFooter());
167     }
168 
169     /**
170      * Gets the conversion pattern.
171      *
172      * @return the conversion pattern.
173      */
174     public String getConversionPattern() {
175         return conversionPattern;
176     }
177 
178     /**
179      * Gets this PatternLayout's content format. Specified by:
180      * <ul>
181      * <li>Key: "structured" Value: "false"</li>
182      * <li>Key: "formatType" Value: "conversion" (format uses the keywords supported by OptionConverter)</li>
183      * <li>Key: "format" Value: provided "conversionPattern" param</li>
184      * </ul>
185      * 
186      * @return Map of content format keys supporting PatternLayout
187      */
188     @Override
189     public Map<String, String> getContentFormat()
190     {
191         final Map<String, String> result = new HashMap<>();
192         result.put("structured", "false");
193         result.put("formatType", "conversion");
194         result.put("format", conversionPattern);
195         return result;
196     }
197 
198     /**
199      * Formats a logging event to a writer.
200      *
201      * @param event logging event to be formatted.
202      * @return The event formatted as a String.
203      */
204     @Override
205     public String toSerializable(final LogEvent event) {
206         return serializer.toSerializable(event);
207     }
208 
209     /**
210      * Creates a PatternParser.
211      * @param config The Configuration.
212      * @return The PatternParser.
213      */
214     public static PatternParser createPatternParser(final Configuration config) {
215         if (config == null) {
216             return new PatternParser(config, KEY, LogEventPatternConverter.class);
217         }
218         PatternParser parser = config.getComponent(KEY);
219         if (parser == null) {
220             parser = new PatternParser(config, KEY, LogEventPatternConverter.class);
221             config.addComponent(KEY, parser);
222             parser = config.getComponent(KEY);
223         }
224         return parser;
225     }
226 
227     @Override
228     public String toString() {
229         return patternSelector == null ? conversionPattern : patternSelector.toString();
230     }
231 
232     /**
233      * Creates a pattern layout.
234      *
235      * @param pattern
236      *        The pattern. If not specified, defaults to DEFAULT_CONVERSION_PATTERN.
237      * @param patternSelector 
238      *        Allows different patterns to be used based on some selection criteria.
239      * @param config
240      *        The Configuration. Some Converters require access to the Interpolator.
241      * @param replace
242      *        A Regex replacement String.
243      * @param charset
244      *        The character set. The platform default is used if not specified.
245      * @param alwaysWriteExceptions
246      *        If {@code "true"} (default) exceptions are always written even if the pattern contains no exception tokens.
247      * @param noConsoleNoAnsi
248      *        If {@code "true"} (default is false) and {@link System#console()} is null, do not output ANSI escape codes
249      * @param header
250      *        The footer to place at the top of the document, once.
251      * @param footer
252      *        The footer to place at the bottom of the document, once.
253      * @return The PatternLayout.
254      */
255     @PluginFactory
256     public static PatternLayout createLayout(
257             @PluginAttribute(value = "pattern", defaultString = DEFAULT_CONVERSION_PATTERN) final String pattern,
258             @PluginElement("PatternSelector") final PatternSelector patternSelector,
259             @PluginConfiguration final Configuration config,
260             @PluginElement("Replace") final RegexReplacement replace,
261             // LOG4J2-783 use platform default by default, so do not specify defaultString for charset
262             @PluginAttribute(value = "charset") final Charset charset,
263             @PluginAttribute(value = "alwaysWriteExceptions", defaultBoolean = true) final boolean alwaysWriteExceptions,
264             @PluginAttribute(value = "noConsoleNoAnsi", defaultBoolean = false) final boolean noConsoleNoAnsi,
265             @PluginAttribute("header") final String header,
266             @PluginAttribute("footer") final String footer) {
267         return newBuilder()
268             .withPattern(pattern)
269             .withPatternSelector(patternSelector)
270             .withConfiguration(config)
271             .withRegexReplacement(replace)
272             .withCharset(charset)
273             .withAlwaysWriteExceptions(alwaysWriteExceptions)
274             .withNoConsoleNoAnsi(noConsoleNoAnsi)
275             .withHeader(header)
276             .withFooter(footer)
277             .build();
278     }
279 
280 
281     private interface Serializer {
282 
283         String toSerializable(final LogEvent event);
284     }
285 
286     private class PatternSerializer implements Serializer {
287         @Override
288         public String toSerializable(final LogEvent event) {
289             final StringBuilder buf = getStringBuilder();
290             final int len = formatters.length;
291             for (int i = 0; i < len; i++) {
292                 formatters[i].format(event, buf);
293             }
294             String str = buf.toString();
295             if (replace != null) {
296                 str = replace.format(str);
297             }
298             return str;
299         }
300     }
301 
302     private class PatternSelectorSerializer implements Serializer {
303         @Override
304         public String toSerializable(final LogEvent event) {
305             final StringBuilder buf = getStringBuilder();
306             PatternFormatter[] formatters = patternSelector.getFormatters(event);
307             final int len = formatters.length;
308             for (int i = 0; i < len; i++) {
309                 formatters[i].format(event, buf);
310             }
311             String str = buf.toString();
312             if (replace != null) {
313                 str = replace.format(str);
314             }
315             return str;
316         }
317     }
318 
319     /**
320      * Creates a PatternLayout using the default options. These options include using UTF-8, the default conversion
321      * pattern, exceptions being written, and with ANSI escape codes.
322      *
323      * @return the PatternLayout.
324      * @see #DEFAULT_CONVERSION_PATTERN Default conversion pattern
325      */
326     public static PatternLayout createDefaultLayout() {
327         return newBuilder().build();
328     }
329 
330     /**
331      * Creates a PatternLayout using the default options and the given
332      * configuration. These options include using UTF-8, the default conversion
333      * pattern, exceptions being written, and with ANSI escape codes.
334      * 
335      * @param configuration The Configuration.
336      *
337      * @return the PatternLayout.
338      * @see #DEFAULT_CONVERSION_PATTERN Default conversion pattern
339      */
340     public static PatternLayout createDefaultLayout(Configuration configuration) {
341         return newBuilder().withConfiguration(configuration).build();
342     }
343 
344     /**
345      * Creates a builder for a custom PatternLayout.
346      * @return a PatternLayout builder.
347      */
348     @PluginBuilderFactory
349     public static Builder newBuilder() {
350         return new Builder();
351     }
352 
353     /**
354      * Custom PatternLayout builder. Use the {@link PatternLayout#newBuilder() builder factory method} to create this.
355      */
356     public static class Builder implements org.apache.logging.log4j.core.util.Builder<PatternLayout> {
357 
358         @PluginBuilderAttribute
359         private String pattern = PatternLayout.DEFAULT_CONVERSION_PATTERN;
360 
361         @PluginElement("PatternSelector")
362         private PatternSelector patternSelector = null;
363 
364         @PluginConfiguration
365         private Configuration configuration = null;
366 
367         @PluginElement("Replace")
368         private RegexReplacement regexReplacement = null;
369 
370         // LOG4J2-783 use platform default by default
371         @PluginBuilderAttribute
372         private Charset charset = Charset.defaultCharset();
373 
374         @PluginBuilderAttribute
375         private boolean alwaysWriteExceptions = true;
376 
377         @PluginBuilderAttribute
378         private boolean noConsoleNoAnsi = false;
379 
380         @PluginBuilderAttribute
381         private String header = null;
382 
383         @PluginBuilderAttribute
384         private String footer = null;
385 
386         private Builder() {
387         }
388 
389         // TODO: move javadocs from PluginFactory to here
390 
391         public Builder withPattern(final String pattern) {
392             this.pattern = pattern;
393             return this;
394         }
395 
396         public Builder withPatternSelector(final PatternSelector patternSelector) {
397             this.patternSelector = patternSelector;
398             return this;
399         }
400 
401 
402         public Builder withConfiguration(final Configuration configuration) {
403             this.configuration = configuration;
404             return this;
405         }
406 
407         public Builder withRegexReplacement(final RegexReplacement regexReplacement) {
408             this.regexReplacement = regexReplacement;
409             return this;
410         }
411 
412         public Builder withCharset(final Charset charset) {
413             // LOG4J2-783 if null, use platform default by default
414             if (charset != null) {
415                 this.charset = charset;
416             }
417             return this;
418         }
419 
420         public Builder withAlwaysWriteExceptions(final boolean alwaysWriteExceptions) {
421             this.alwaysWriteExceptions = alwaysWriteExceptions;
422             return this;
423         }
424 
425         public Builder withNoConsoleNoAnsi(final boolean noConsoleNoAnsi) {
426             this.noConsoleNoAnsi = noConsoleNoAnsi;
427             return this;
428         }
429 
430         public Builder withHeader(final String header) {
431             this.header = header;
432             return this;
433         }
434 
435         public Builder withFooter(final String footer) {
436             this.footer = footer;
437             return this;
438         }
439 
440         @Override
441         public PatternLayout build() {
442             // fall back to DefaultConfiguration
443             if (configuration == null) {
444                 configuration = new DefaultConfiguration();
445             }
446             return new PatternLayout(configuration, regexReplacement, pattern, patternSelector, charset,
447                 alwaysWriteExceptions, noConsoleNoAnsi, header, footer);
448         }
449     }
450 }