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 }