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