    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     */
    017    package org.apache.logging.log4j.core.layout;
    018
    019    import java.nio.charset.Charset;
    020    import java.util.HashMap;
    021    import java.util.List;
    022    import java.util.Map;
    023
    024    import org.apache.logging.log4j.core.Layout;
    025    import org.apache.logging.log4j.core.LogEvent;
    026    import org.apache.logging.log4j.core.config.Configuration;
    027    import org.apache.logging.log4j.core.config.DefaultConfiguration;
    028    import org.apache.logging.log4j.core.config.Node;
    029    import org.apache.logging.log4j.core.config.plugins.Plugin;
    030    import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
    031    import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
    032    import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
    033    import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
    034    import org.apache.logging.log4j.core.config.plugins.PluginElement;
    035    import org.apache.logging.log4j.core.config.plugins.PluginFactory;
    036    import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
    037    import org.apache.logging.log4j.core.pattern.PatternFormatter;
    038    import org.apache.logging.log4j.core.pattern.PatternParser;
    039    import org.apache.logging.log4j.core.pattern.RegexReplacement;
    040
    041    /**
    042     * A flexible layout configurable with pattern string.
    043     * <p>
    044     * The goal of this class is to {@link org.apache.logging.log4j.core.Layout#toByteArray format} a {@link LogEvent} and
    045     * return the results. The format of the result depends on the <em>conversion pattern</em>.
    046     * </p>
    047     * <p>
    048     * The conversion pattern is closely related to the conversion pattern of the printf function in C. A conversion pattern
    049     * is composed of literal text and format control expressions called <em>conversion specifiers</em>.
    050     * </p>
    051     * <p>
    052     * See the Log4j Manual for details on the supported pattern converters.
    053     * </p>
    054     */
    055    @Plugin(name = "PatternLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
    056    public final class PatternLayout extends AbstractStringLayout {
    057
    058        private static final long serialVersionUID = 1L;
    059
    060        /**
    061         * Default pattern string for log output. Currently set to the
    062         * string <b>"%m%n"</b> which just prints the application supplied
    063         * message.
    064         */
    065        public static final String DEFAULT_CONVERSION_PATTERN = "%m%n";
    066
    067        /**
    068         * A conversion pattern equivalent to the TTCCCLayout.
    069         * Current value is <b>%r [%t] %p %c %x - %m%n</b>.
    070         */
    071        public static final String TTCC_CONVERSION_PATTERN =
    072            "%r [%t] %p %c %x - %m%n";
    073
    074        /**
    075         * A simple pattern.
    076         * Current value is <b>%d [%t] %p %c - %m%n</b>.
    077         */
    078        public static final String SIMPLE_CONVERSION_PATTERN =
    079            "%d [%t] %p %c - %m%n";
    080
    081        /** Key to identify pattern converters. */
    082        public static final String KEY = "Converter";
    083
    084        /**
    085         * Initial converter for pattern.
    086         */
    087        private final List<PatternFormatter> formatters;
    088
    089        /**
    090         * Conversion pattern.
    091         */
    092        private final String conversionPattern;
    093
    094
    095        /**
    096         * The current Configuration.
    097         */
    098        private final Configuration config;
    099
    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    }