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