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