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.Layout; 025import org.apache.logging.log4j.core.LogEvent; 026import org.apache.logging.log4j.core.config.Configuration; 027import org.apache.logging.log4j.core.config.DefaultConfiguration; 028import org.apache.logging.log4j.core.config.Node; 029import org.apache.logging.log4j.core.config.plugins.Plugin; 030import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 031import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; 032import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; 033import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; 034import org.apache.logging.log4j.core.config.plugins.PluginElement; 035import org.apache.logging.log4j.core.config.plugins.PluginFactory; 036import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; 037import org.apache.logging.log4j.core.pattern.PatternFormatter; 038import org.apache.logging.log4j.core.pattern.PatternParser; 039import 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) 056public 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}