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; 040import org.apache.logging.log4j.core.util.StringEncoder; 041 042/** 043 * A flexible layout configurable with pattern string. 044 * <p> 045 * The goal of this class is to {@link org.apache.logging.log4j.core.Layout#toByteArray format} a {@link LogEvent} and 046 * return the results. The format of the result depends on the <em>conversion pattern</em>. 047 * </p> 048 * <p> 049 * The conversion pattern is closely related to the conversion pattern of the printf function in C. A conversion pattern 050 * is composed of literal text and format control expressions called <em>conversion specifiers</em>. 051 * </p> 052 * <p> 053 * See the Log4j Manual for details on the supported pattern converters. 054 * </p> 055 */ 056@Plugin(name = "PatternLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) 057public final class PatternLayout extends AbstractStringLayout { 058 059 /** 060 * Default pattern string for log output. Currently set to the 061 * string <b>"%m%n"</b> which just prints the application supplied 062 * message. 063 */ 064 public static final String DEFAULT_CONVERSION_PATTERN = "%m%n"; 065 066 /** 067 * A conversion pattern equivalent to the TTCCCLayout. 068 * Current value is <b>%r [%t] %p %c %x - %m%n</b>. 069 */ 070 public static final String TTCC_CONVERSION_PATTERN = 071 "%r [%t] %p %c %x - %m%n"; 072 073 /** 074 * A simple pattern. 075 * Current value is <b>%d [%t] %p %c - %m%n</b>. 076 */ 077 public static final String SIMPLE_CONVERSION_PATTERN = 078 "%d [%t] %p %c - %m%n"; 079 080 /** Key to identify pattern converters. */ 081 public static final String KEY = "Converter"; 082 083 private static final long serialVersionUID = 1L; 084 085 /** 086 * Initial converter for pattern. 087 */ 088 private final PatternFormatter[] formatters; 089 090 /** 091 * Conversion pattern. 092 */ 093 private final String conversionPattern; 094 095 private final PatternSelector patternSelector; 096 097 private final Serializer serializer; 098 099 100 /** 101 * The current Configuration. 102 */ 103 private final Configuration config; 104 105 private final RegexReplacement replace; 106 107 private final boolean alwaysWriteExceptions; 108 109 private final boolean noConsoleNoAnsi; 110 111 /** 112 * Constructs a EnhancedPatternLayout using the supplied conversion pattern. 113 * 114 * @param config The Configuration. 115 * @param replace The regular expression to match. 116 * @param pattern conversion pattern. 117 * @param patternSelector The PatternSelector. 118 * @param charset The character set. 119 * @param alwaysWriteExceptions Whether or not exceptions should always be handled in this pattern (if {@code true}, 120 * exceptions will be written even if the pattern does not specify so). 121 * @param noConsoleNoAnsi 122 * If {@code "true"} (default) and {@link System#console()} is null, do not output ANSI escape codes 123 * @param header 124 */ 125 private PatternLayout(final Configuration config, final RegexReplacement replace, final String pattern, 126 final PatternSelector patternSelector, final Charset charset, 127 final boolean alwaysWriteExceptions, final boolean noConsoleNoAnsi, 128 final String header, final String footer) { 129 super(charset, StringEncoder.toBytes(header, charset), StringEncoder.toBytes(footer, charset)); 130 this.replace = replace; 131 this.conversionPattern = pattern; 132 this.patternSelector = patternSelector; 133 this.config = config; 134 this.alwaysWriteExceptions = alwaysWriteExceptions; 135 this.noConsoleNoAnsi = noConsoleNoAnsi; 136 if (patternSelector == null) { 137 serializer = new PatternSerializer(); 138 final PatternParser parser = createPatternParser(config); 139 try { 140 List<PatternFormatter> list = parser.parse(pattern == null ? DEFAULT_CONVERSION_PATTERN : pattern, 141 this.alwaysWriteExceptions, this.noConsoleNoAnsi); 142 this.formatters = list.toArray(new PatternFormatter[0]); 143 } catch (RuntimeException ex) { 144 throw new IllegalArgumentException("Cannot parse pattern '" + pattern + "'", ex); 145 } 146 } else { 147 this.formatters = null; 148 serializer = new PatternSelectorSerializer(); 149 } 150 } 151 152 private byte[] strSubstitutorReplace(final byte... b) { 153 if (b != null && config != null) { 154 return getBytes(config.getStrSubstitutor().replace(new String(b, getCharset()))); 155 } 156 return b; 157 } 158 159 @Override 160 public byte[] getHeader() { 161 return strSubstitutorReplace(super.getHeader()); 162 } 163 164 @Override 165 public byte[] getFooter() { 166 return strSubstitutorReplace(super.getFooter()); 167 } 168 169 /** 170 * Gets the conversion pattern. 171 * 172 * @return the conversion pattern. 173 */ 174 public String getConversionPattern() { 175 return conversionPattern; 176 } 177 178 /** 179 * Gets this PatternLayout's content format. Specified by: 180 * <ul> 181 * <li>Key: "structured" Value: "false"</li> 182 * <li>Key: "formatType" Value: "conversion" (format uses the keywords supported by OptionConverter)</li> 183 * <li>Key: "format" Value: provided "conversionPattern" param</li> 184 * </ul> 185 * 186 * @return Map of content format keys supporting PatternLayout 187 */ 188 @Override 189 public Map<String, String> getContentFormat() 190 { 191 final Map<String, String> result = new HashMap<>(); 192 result.put("structured", "false"); 193 result.put("formatType", "conversion"); 194 result.put("format", conversionPattern); 195 return result; 196 } 197 198 /** 199 * Formats a logging event to a writer. 200 * 201 * @param event logging event to be formatted. 202 * @return The event formatted as a String. 203 */ 204 @Override 205 public String toSerializable(final LogEvent event) { 206 return serializer.toSerializable(event); 207 } 208 209 /** 210 * Creates a PatternParser. 211 * @param config The Configuration. 212 * @return The PatternParser. 213 */ 214 public static PatternParser createPatternParser(final Configuration config) { 215 if (config == null) { 216 return new PatternParser(config, KEY, LogEventPatternConverter.class); 217 } 218 PatternParser parser = config.getComponent(KEY); 219 if (parser == null) { 220 parser = new PatternParser(config, KEY, LogEventPatternConverter.class); 221 config.addComponent(KEY, parser); 222 parser = config.getComponent(KEY); 223 } 224 return parser; 225 } 226 227 @Override 228 public String toString() { 229 return patternSelector == null ? conversionPattern : patternSelector.toString(); 230 } 231 232 /** 233 * Creates a pattern layout. 234 * 235 * @param pattern 236 * The pattern. If not specified, defaults to DEFAULT_CONVERSION_PATTERN. 237 * @param patternSelector 238 * Allows different patterns to be used based on some selection criteria. 239 * @param config 240 * The Configuration. Some Converters require access to the Interpolator. 241 * @param replace 242 * A Regex replacement String. 243 * @param charset 244 * The character set. The platform default is used if not specified. 245 * @param alwaysWriteExceptions 246 * If {@code "true"} (default) exceptions are always written even if the pattern contains no exception tokens. 247 * @param noConsoleNoAnsi 248 * If {@code "true"} (default is false) and {@link System#console()} is null, do not output ANSI escape codes 249 * @param header 250 * The footer to place at the top of the document, once. 251 * @param footer 252 * The footer to place at the bottom of the document, once. 253 * @return The PatternLayout. 254 */ 255 @PluginFactory 256 public static PatternLayout createLayout( 257 @PluginAttribute(value = "pattern", defaultString = DEFAULT_CONVERSION_PATTERN) final String pattern, 258 @PluginElement("PatternSelector") final PatternSelector patternSelector, 259 @PluginConfiguration final Configuration config, 260 @PluginElement("Replace") final RegexReplacement replace, 261 // LOG4J2-783 use platform default by default, so do not specify defaultString for charset 262 @PluginAttribute(value = "charset") final Charset charset, 263 @PluginAttribute(value = "alwaysWriteExceptions", defaultBoolean = true) final boolean alwaysWriteExceptions, 264 @PluginAttribute(value = "noConsoleNoAnsi", defaultBoolean = false) final boolean noConsoleNoAnsi, 265 @PluginAttribute("header") final String header, 266 @PluginAttribute("footer") final String footer) { 267 return newBuilder() 268 .withPattern(pattern) 269 .withPatternSelector(patternSelector) 270 .withConfiguration(config) 271 .withRegexReplacement(replace) 272 .withCharset(charset) 273 .withAlwaysWriteExceptions(alwaysWriteExceptions) 274 .withNoConsoleNoAnsi(noConsoleNoAnsi) 275 .withHeader(header) 276 .withFooter(footer) 277 .build(); 278 } 279 280 281 private interface Serializer { 282 283 String toSerializable(final LogEvent event); 284 } 285 286 private class PatternSerializer implements Serializer { 287 @Override 288 public String toSerializable(final LogEvent event) { 289 final StringBuilder buf = getStringBuilder(); 290 final int len = formatters.length; 291 for (int i = 0; i < len; i++) { 292 formatters[i].format(event, buf); 293 } 294 String str = buf.toString(); 295 if (replace != null) { 296 str = replace.format(str); 297 } 298 return str; 299 } 300 } 301 302 private class PatternSelectorSerializer implements Serializer { 303 @Override 304 public String toSerializable(final LogEvent event) { 305 final StringBuilder buf = getStringBuilder(); 306 PatternFormatter[] formatters = patternSelector.getFormatters(event); 307 final int len = formatters.length; 308 for (int i = 0; i < len; i++) { 309 formatters[i].format(event, buf); 310 } 311 String str = buf.toString(); 312 if (replace != null) { 313 str = replace.format(str); 314 } 315 return str; 316 } 317 } 318 319 /** 320 * Creates a PatternLayout using the default options. These options include using UTF-8, the default conversion 321 * pattern, exceptions being written, and with ANSI escape codes. 322 * 323 * @return the PatternLayout. 324 * @see #DEFAULT_CONVERSION_PATTERN Default conversion pattern 325 */ 326 public static PatternLayout createDefaultLayout() { 327 return newBuilder().build(); 328 } 329 330 /** 331 * Creates a PatternLayout using the default options and the given 332 * configuration. These options include using UTF-8, the default conversion 333 * pattern, exceptions being written, and with ANSI escape codes. 334 * 335 * @param configuration The Configuration. 336 * 337 * @return the PatternLayout. 338 * @see #DEFAULT_CONVERSION_PATTERN Default conversion pattern 339 */ 340 public static PatternLayout createDefaultLayout(Configuration configuration) { 341 return newBuilder().withConfiguration(configuration).build(); 342 } 343 344 /** 345 * Creates a builder for a custom PatternLayout. 346 * @return a PatternLayout builder. 347 */ 348 @PluginBuilderFactory 349 public static Builder newBuilder() { 350 return new Builder(); 351 } 352 353 /** 354 * Custom PatternLayout builder. Use the {@link PatternLayout#newBuilder() builder factory method} to create this. 355 */ 356 public static class Builder implements org.apache.logging.log4j.core.util.Builder<PatternLayout> { 357 358 @PluginBuilderAttribute 359 private String pattern = PatternLayout.DEFAULT_CONVERSION_PATTERN; 360 361 @PluginElement("PatternSelector") 362 private PatternSelector patternSelector = null; 363 364 @PluginConfiguration 365 private Configuration configuration = null; 366 367 @PluginElement("Replace") 368 private RegexReplacement regexReplacement = null; 369 370 // LOG4J2-783 use platform default by default 371 @PluginBuilderAttribute 372 private Charset charset = Charset.defaultCharset(); 373 374 @PluginBuilderAttribute 375 private boolean alwaysWriteExceptions = true; 376 377 @PluginBuilderAttribute 378 private boolean noConsoleNoAnsi = false; 379 380 @PluginBuilderAttribute 381 private String header = null; 382 383 @PluginBuilderAttribute 384 private String footer = null; 385 386 private Builder() { 387 } 388 389 // TODO: move javadocs from PluginFactory to here 390 391 public Builder withPattern(final String pattern) { 392 this.pattern = pattern; 393 return this; 394 } 395 396 public Builder withPatternSelector(final PatternSelector patternSelector) { 397 this.patternSelector = patternSelector; 398 return this; 399 } 400 401 402 public Builder withConfiguration(final Configuration configuration) { 403 this.configuration = configuration; 404 return this; 405 } 406 407 public Builder withRegexReplacement(final RegexReplacement regexReplacement) { 408 this.regexReplacement = regexReplacement; 409 return this; 410 } 411 412 public Builder withCharset(final Charset charset) { 413 // LOG4J2-783 if null, use platform default by default 414 if (charset != null) { 415 this.charset = charset; 416 } 417 return this; 418 } 419 420 public Builder withAlwaysWriteExceptions(final boolean alwaysWriteExceptions) { 421 this.alwaysWriteExceptions = alwaysWriteExceptions; 422 return this; 423 } 424 425 public Builder withNoConsoleNoAnsi(final boolean noConsoleNoAnsi) { 426 this.noConsoleNoAnsi = noConsoleNoAnsi; 427 return this; 428 } 429 430 public Builder withHeader(final String header) { 431 this.header = header; 432 return this; 433 } 434 435 public Builder withFooter(final String footer) { 436 this.footer = footer; 437 return this; 438 } 439 440 @Override 441 public PatternLayout build() { 442 // fall back to DefaultConfiguration 443 if (configuration == null) { 444 configuration = new DefaultConfiguration(); 445 } 446 return new PatternLayout(configuration, regexReplacement, pattern, patternSelector, charset, 447 alwaysWriteExceptions, noConsoleNoAnsi, header, footer); 448 } 449 } 450}