001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.core.layout;
018
019import java.nio.charset.Charset;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.logging.log4j.core.LogEvent;
025import org.apache.logging.log4j.core.config.Configuration;
026import org.apache.logging.log4j.core.config.DefaultConfiguration;
027import org.apache.logging.log4j.core.config.plugins.Plugin;
028import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
029import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
030import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
031import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
032import org.apache.logging.log4j.core.config.plugins.PluginElement;
033import org.apache.logging.log4j.core.config.plugins.PluginFactory;
034import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
035import org.apache.logging.log4j.core.pattern.PatternFormatter;
036import org.apache.logging.log4j.core.pattern.PatternParser;
037import org.apache.logging.log4j.core.pattern.RegexReplacement;
038import org.apache.logging.log4j.core.util.Charsets;
039
040/**
041 * <p>A flexible layout configurable with pattern string. The goal of this class
042 * is to {@link org.apache.logging.log4j.core.Layout#toByteArray format} a {@link LogEvent} and return the results.
043 * The format of the result depends on the <em>conversion pattern</em>.
044 * <p>
045 * <p/>
046 * <p>The conversion pattern is closely related to the conversion
047 * pattern of the printf function in C. A conversion pattern is
048 * composed of literal text and format control expressions called
049 * <em>conversion specifiers</em>.
050 *
051 * See the Log4j Manual for details on the supported pattern converters.
052 */
053@Plugin(name = "PatternLayout", category = "Core", elementType = "layout", printObject = true)
054public final class PatternLayout extends AbstractStringLayout {
055    /**
056     * Default pattern string for log output. Currently set to the
057     * string <b>"%m%n"</b> which just prints the application supplied
058     * message.
059     */
060    public static final String DEFAULT_CONVERSION_PATTERN = "%m%n";
061
062    /**
063     * A conversion pattern equivalent to the TTCCCLayout.
064     * Current value is <b>%r [%t] %p %c %x - %m%n</b>.
065     */
066    public static final String TTCC_CONVERSION_PATTERN =
067        "%r [%t] %p %c %x - %m%n";
068
069    /**
070     * A simple pattern.
071     * Current value is <b>%d [%t] %p %c - %m%n</b>.
072     */
073    public static final String SIMPLE_CONVERSION_PATTERN =
074        "%d [%t] %p %c - %m%n";
075
076    /** Key to identify pattern converters. */
077    public static final String KEY = "Converter";
078
079    /**
080     * Initial converter for pattern.
081     */
082    private final List<PatternFormatter> formatters;
083
084    /**
085     * Conversion pattern.
086     */
087    private final String conversionPattern;
088
089
090    /**
091     * The current Configuration.
092     */
093    private final Configuration config;
094
095    private final RegexReplacement replace;
096
097    private final boolean alwaysWriteExceptions;
098
099    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}