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