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