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