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.impl.LocationAware; 038import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; 039import org.apache.logging.log4j.core.pattern.PatternFormatter; 040import org.apache.logging.log4j.core.pattern.PatternParser; 041import org.apache.logging.log4j.core.pattern.RegexReplacement; 042import org.apache.logging.log4j.util.PropertiesUtil; 043import org.apache.logging.log4j.util.Strings; 044 045/** 046 * A flexible layout configurable with pattern string. 047 * <p> 048 * The goal of this class is to {@link org.apache.logging.log4j.core.Layout#toByteArray format} a {@link LogEvent} and 049 * return the results. The format of the result depends on the <em>conversion pattern</em>. 050 * </p> 051 * <p> 052 * The conversion pattern is closely related to the conversion pattern of the printf function in C. A conversion pattern 053 * is composed of literal text and format control expressions called <em>conversion specifiers</em>. 054 * </p> 055 * <p> 056 * See the Log4j Manual for details on the supported pattern converters. 057 * </p> 058 */ 059@Plugin(name = "PatternLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) 060public final class PatternLayout extends AbstractStringLayout { 061 062 /** 063 * Default pattern string for log output. Currently set to the string <b>"%m%n"</b> which just prints the 064 * application supplied message. 065 */ 066 public static final String DEFAULT_CONVERSION_PATTERN = "%m%n"; 067 068 /** 069 * A conversion pattern equivalent to the TTCCLayout. Current value is <b>%r [%t] %p %c %notEmpty{%x }- %m%n</b>. 070 */ 071 public static final String TTCC_CONVERSION_PATTERN = "%r [%t] %p %c %notEmpty{%x }- %m%n"; 072 073 /** 074 * A simple pattern. Current value is <b>%d [%t] %p %c - %m%n</b>. 075 */ 076 public static final String SIMPLE_CONVERSION_PATTERN = "%d [%t] %p %c - %m%n"; 077 078 /** Key to identify pattern converters. */ 079 public static final String KEY = "Converter"; 080 081 /** 082 * Conversion pattern. 083 */ 084 private final String conversionPattern; 085 private final PatternSelector patternSelector; 086 private final Serializer eventSerializer; 087 088 /** 089 * Constructs a PatternLayout using the supplied conversion pattern. 090 * 091 * @param config The Configuration. 092 * @param replace The regular expression to match. 093 * @param eventPattern conversion pattern. 094 * @param patternSelector The PatternSelector. 095 * @param charset The character set. 096 * @param alwaysWriteExceptions Whether or not exceptions should always be handled in this pattern (if {@code true}, 097 * exceptions will be written even if the pattern does not specify so). 098 * @param disableAnsi 099 * If {@code "true"}, do not output ANSI escape codes 100 * @param noConsoleNoAnsi 101 * If {@code "true"} (default) and {@link System#console()} is null, do not output ANSI escape codes 102 * @param headerPattern header conversion pattern. 103 * @param footerPattern footer conversion pattern. 104 */ 105 private PatternLayout(final Configuration config, final RegexReplacement replace, final String eventPattern, 106 final PatternSelector patternSelector, final Charset charset, final boolean alwaysWriteExceptions, 107 final boolean disableAnsi, final boolean noConsoleNoAnsi, final String headerPattern, 108 final String footerPattern) { 109 super(config, charset, 110 newSerializerBuilder() 111 .setConfiguration(config) 112 .setReplace(replace) 113 .setPatternSelector(patternSelector) 114 .setAlwaysWriteExceptions(alwaysWriteExceptions) 115 .setDisableAnsi(disableAnsi) 116 .setNoConsoleNoAnsi(noConsoleNoAnsi) 117 .setPattern(headerPattern) 118 .build(), 119 newSerializerBuilder() 120 .setConfiguration(config) 121 .setReplace(replace) 122 .setPatternSelector(patternSelector) 123 .setAlwaysWriteExceptions(alwaysWriteExceptions) 124 .setDisableAnsi(disableAnsi) 125 .setNoConsoleNoAnsi(noConsoleNoAnsi) 126 .setPattern(footerPattern) 127 .build()); 128 this.conversionPattern = eventPattern; 129 this.patternSelector = patternSelector; 130 this.eventSerializer = newSerializerBuilder() 131 .setConfiguration(config) 132 .setReplace(replace) 133 .setPatternSelector(patternSelector) 134 .setAlwaysWriteExceptions(alwaysWriteExceptions) 135 .setDisableAnsi(disableAnsi) 136 .setNoConsoleNoAnsi(noConsoleNoAnsi) 137 .setPattern(eventPattern) 138 .setDefaultPattern(DEFAULT_CONVERSION_PATTERN) 139 .build(); 140 } 141 142 public static SerializerBuilder newSerializerBuilder() { 143 return new SerializerBuilder(); 144 } 145 146 @Override 147 public boolean requiresLocation() { 148 return eventSerializer instanceof LocationAware && ((LocationAware) eventSerializer).requiresLocation(); 149 } 150 151 152 /** 153 * Deprecated, use {@link #newSerializerBuilder()} instead. 154 * 155 * @param configuration 156 * @param replace 157 * @param pattern 158 * @param defaultPattern 159 * @param patternSelector 160 * @param alwaysWriteExceptions 161 * @param noConsoleNoAnsi 162 * @return a new Serializer. 163 * @deprecated Use {@link #newSerializerBuilder()} instead. 164 */ 165 @Deprecated 166 public static Serializer createSerializer(final Configuration configuration, final RegexReplacement replace, 167 final String pattern, final String defaultPattern, final PatternSelector patternSelector, 168 final boolean alwaysWriteExceptions, final boolean noConsoleNoAnsi) { 169 final SerializerBuilder builder = newSerializerBuilder(); 170 builder.setAlwaysWriteExceptions(alwaysWriteExceptions); 171 builder.setConfiguration(configuration); 172 builder.setDefaultPattern(defaultPattern); 173 builder.setNoConsoleNoAnsi(noConsoleNoAnsi); 174 builder.setPattern(pattern); 175 builder.setPatternSelector(patternSelector); 176 builder.setReplace(replace); 177 return builder.build(); 178 } 179 180 /** 181 * Gets the conversion pattern. 182 * 183 * @return the conversion pattern. 184 */ 185 public String getConversionPattern() { 186 return conversionPattern; 187 } 188 189 /** 190 * Gets this PatternLayout's content format. Specified by: 191 * <ul> 192 * <li>Key: "structured" Value: "false"</li> 193 * <li>Key: "formatType" Value: "conversion" (format uses the keywords supported by OptionConverter)</li> 194 * <li>Key: "format" Value: provided "conversionPattern" param</li> 195 * </ul> 196 * 197 * @return Map of content format keys supporting PatternLayout 198 */ 199 @Override 200 public Map<String, String> getContentFormat() { 201 final Map<String, String> result = new HashMap<>(); 202 result.put("structured", "false"); 203 result.put("formatType", "conversion"); 204 result.put("format", conversionPattern); 205 return result; 206 } 207 208 /** 209 * Formats a logging event to a writer. 210 * 211 * @param event logging event to be formatted. 212 * @return The event formatted as a String. 213 */ 214 @Override 215 public String toSerializable(final LogEvent event) { 216 return eventSerializer.toSerializable(event); 217 } 218 219 @Override 220 public void encode(final LogEvent event, final ByteBufferDestination destination) { 221 if (!(eventSerializer instanceof Serializer2)) { 222 super.encode(event, destination); 223 return; 224 } 225 final StringBuilder text = toText((Serializer2) eventSerializer, event, getStringBuilder()); 226 final Encoder<StringBuilder> encoder = getStringBuilderEncoder(); 227 encoder.encode(text, destination); 228 trimToMaxSize(text); 229 } 230 231 /** 232 * Creates a text representation of the specified log event 233 * and writes it into the specified StringBuilder. 234 * <p> 235 * Implementations are free to return a new StringBuilder if they can 236 * detect in advance that the specified StringBuilder is too small. 237 */ 238 private StringBuilder toText(final Serializer2 serializer, final LogEvent event, 239 final StringBuilder destination) { 240 return serializer.toSerializable(event, destination); 241 } 242 243 /** 244 * Creates a PatternParser. 245 * @param config The Configuration. 246 * @return The PatternParser. 247 */ 248 public static PatternParser createPatternParser(final Configuration config) { 249 if (config == null) { 250 return new PatternParser(config, KEY, LogEventPatternConverter.class); 251 } 252 PatternParser parser = config.getComponent(KEY); 253 if (parser == null) { 254 parser = new PatternParser(config, KEY, LogEventPatternConverter.class); 255 config.addComponent(KEY, parser); 256 parser = config.getComponent(KEY); 257 } 258 return parser; 259 } 260 261 @Override 262 public String toString() { 263 return patternSelector == null ? conversionPattern : patternSelector.toString(); 264 } 265 266 /** 267 * Creates a pattern layout. 268 * 269 * @param pattern 270 * The pattern. If not specified, defaults to DEFAULT_CONVERSION_PATTERN. 271 * @param patternSelector 272 * Allows different patterns to be used based on some selection criteria. 273 * @param config 274 * The Configuration. Some Converters require access to the Interpolator. 275 * @param replace 276 * A Regex replacement String. 277 * @param charset 278 * The character set. The platform default is used if not specified. 279 * @param alwaysWriteExceptions 280 * If {@code "true"} (default) exceptions are always written even if the pattern contains no exception tokens. 281 * @param noConsoleNoAnsi 282 * If {@code "true"} (default is false) and {@link System#console()} is null, do not output ANSI escape codes 283 * @param headerPattern 284 * The footer to place at the top of the document, once. 285 * @param footerPattern 286 * The footer to place at the bottom of the document, once. 287 * @return The PatternLayout. 288 * @deprecated Use {@link #newBuilder()} instead. This will be private in a future version. 289 */ 290 @PluginFactory 291 @Deprecated 292 public static PatternLayout createLayout( 293 @PluginAttribute(value = "pattern", defaultString = DEFAULT_CONVERSION_PATTERN) final String pattern, 294 @PluginElement("PatternSelector") final PatternSelector patternSelector, 295 @PluginConfiguration final Configuration config, 296 @PluginElement("Replace") final RegexReplacement replace, 297 // LOG4J2-783 use platform default by default, so do not specify defaultString for charset 298 @PluginAttribute(value = "charset") final Charset charset, 299 @PluginAttribute(value = "alwaysWriteExceptions", defaultBoolean = true) final boolean alwaysWriteExceptions, 300 @PluginAttribute(value = "noConsoleNoAnsi") final boolean noConsoleNoAnsi, 301 @PluginAttribute("header") final String headerPattern, 302 @PluginAttribute("footer") final String footerPattern) { 303 return newBuilder() 304 .withPattern(pattern) 305 .withPatternSelector(patternSelector) 306 .withConfiguration(config) 307 .withRegexReplacement(replace) 308 .withCharset(charset) 309 .withAlwaysWriteExceptions(alwaysWriteExceptions) 310 .withNoConsoleNoAnsi(noConsoleNoAnsi) 311 .withHeader(headerPattern) 312 .withFooter(footerPattern) 313 .build(); 314 } 315 316 private static class PatternSerializer implements Serializer, Serializer2, LocationAware { 317 318 private final PatternFormatter[] formatters; 319 private final RegexReplacement replace; 320 321 private PatternSerializer(final PatternFormatter[] formatters, final RegexReplacement replace) { 322 super(); 323 this.formatters = formatters; 324 this.replace = replace; 325 } 326 327 @Override 328 public String toSerializable(final LogEvent event) { 329 final StringBuilder sb = getStringBuilder(); 330 try { 331 return toSerializable(event, sb).toString(); 332 } finally { 333 trimToMaxSize(sb); 334 } 335 } 336 337 @Override 338 public StringBuilder toSerializable(final LogEvent event, final StringBuilder buffer) { 339 final int len = formatters.length; 340 for (int i = 0; i < len; i++) { 341 formatters[i].format(event, buffer); 342 } 343 if (replace != null) { // creates temporary objects 344 String str = buffer.toString(); 345 str = replace.format(str); 346 buffer.setLength(0); 347 buffer.append(str); 348 } 349 return buffer; 350 } 351 352 @Override 353 public boolean requiresLocation() { 354 for (PatternFormatter formatter : formatters) { 355 if (formatter.requiresLocation()) { 356 return true; 357 } 358 } 359 return false; 360 } 361 362 @Override 363 public String toString() { 364 final StringBuilder builder = new StringBuilder(); 365 builder.append(super.toString()); 366 builder.append("[formatters="); 367 builder.append(Arrays.toString(formatters)); 368 builder.append(", replace="); 369 builder.append(replace); 370 builder.append("]"); 371 return builder.toString(); 372 } 373 } 374 375 public static class SerializerBuilder implements org.apache.logging.log4j.core.util.Builder<Serializer> { 376 377 private Configuration configuration; 378 private RegexReplacement replace; 379 private String pattern; 380 private String defaultPattern; 381 private PatternSelector patternSelector; 382 private boolean alwaysWriteExceptions; 383 private boolean disableAnsi; 384 private boolean noConsoleNoAnsi; 385 386 @Override 387 public Serializer build() { 388 if (Strings.isEmpty(pattern) && Strings.isEmpty(defaultPattern)) { 389 return null; 390 } 391 if (patternSelector == null) { 392 try { 393 final PatternParser parser = createPatternParser(configuration); 394 final List<PatternFormatter> list = parser.parse(pattern == null ? defaultPattern : pattern, 395 alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi); 396 final PatternFormatter[] formatters = list.toArray(new PatternFormatter[0]); 397 return new PatternSerializer(formatters, replace); 398 } catch (final RuntimeException ex) { 399 throw new IllegalArgumentException("Cannot parse pattern '" + pattern + "'", ex); 400 } 401 } 402 return new PatternSelectorSerializer(patternSelector, replace); 403 } 404 405 public SerializerBuilder setConfiguration(final Configuration configuration) { 406 this.configuration = configuration; 407 return this; 408 } 409 410 public SerializerBuilder setReplace(final RegexReplacement replace) { 411 this.replace = replace; 412 return this; 413 } 414 415 public SerializerBuilder setPattern(final String pattern) { 416 this.pattern = pattern; 417 return this; 418 } 419 420 public SerializerBuilder setDefaultPattern(final String defaultPattern) { 421 this.defaultPattern = defaultPattern; 422 return this; 423 } 424 425 public SerializerBuilder setPatternSelector(final PatternSelector patternSelector) { 426 this.patternSelector = patternSelector; 427 return this; 428 } 429 430 public SerializerBuilder setAlwaysWriteExceptions(final boolean alwaysWriteExceptions) { 431 this.alwaysWriteExceptions = alwaysWriteExceptions; 432 return this; 433 } 434 435 public SerializerBuilder setDisableAnsi(final boolean disableAnsi) { 436 this.disableAnsi = disableAnsi; 437 return this; 438 } 439 440 public SerializerBuilder setNoConsoleNoAnsi(final boolean noConsoleNoAnsi) { 441 this.noConsoleNoAnsi = noConsoleNoAnsi; 442 return this; 443 } 444 445 } 446 447 private static class PatternSelectorSerializer implements Serializer, Serializer2, LocationAware { 448 449 private final PatternSelector patternSelector; 450 private final RegexReplacement replace; 451 452 private PatternSelectorSerializer(final PatternSelector patternSelector, final RegexReplacement replace) { 453 super(); 454 this.patternSelector = patternSelector; 455 this.replace = replace; 456 } 457 458 @Override 459 public String toSerializable(final LogEvent event) { 460 final StringBuilder sb = getStringBuilder(); 461 try { 462 return toSerializable(event, sb).toString(); 463 } finally { 464 trimToMaxSize(sb); 465 } 466 } 467 468 @Override 469 public StringBuilder toSerializable(final LogEvent event, final StringBuilder buffer) { 470 final PatternFormatter[] formatters = patternSelector.getFormatters(event); 471 final int len = formatters.length; 472 for (int i = 0; i < len; i++) { 473 formatters[i].format(event, buffer); 474 } 475 if (replace != null) { // creates temporary objects 476 String str = buffer.toString(); 477 str = replace.format(str); 478 buffer.setLength(0); 479 buffer.append(str); 480 } 481 return buffer; 482 } 483 484 @Override 485 public boolean requiresLocation() { 486 return patternSelector instanceof LocationAware && ((LocationAware) patternSelector).requiresLocation(); 487 } 488 489 @Override 490 public String toString() { 491 final StringBuilder builder = new StringBuilder(); 492 builder.append(super.toString()); 493 builder.append("[patternSelector="); 494 builder.append(patternSelector); 495 builder.append(", replace="); 496 builder.append(replace); 497 builder.append("]"); 498 return builder.toString(); 499 } 500 } 501 502 /** 503 * Creates a PatternLayout using the default options. These options include using UTF-8, the default conversion 504 * pattern, exceptions being written, and with ANSI escape codes. 505 * 506 * @return the PatternLayout. 507 * @see #DEFAULT_CONVERSION_PATTERN Default conversion pattern 508 */ 509 public static PatternLayout createDefaultLayout() { 510 return newBuilder().build(); 511 } 512 513 /** 514 * Creates a PatternLayout using the default options and the given configuration. These options include using UTF-8, 515 * the default conversion pattern, exceptions being written, and with ANSI escape codes. 516 * 517 * @param configuration The Configuration. 518 * 519 * @return the PatternLayout. 520 * @see #DEFAULT_CONVERSION_PATTERN Default conversion pattern 521 */ 522 public static PatternLayout createDefaultLayout(final Configuration configuration) { 523 return newBuilder().withConfiguration(configuration).build(); 524 } 525 526 /** 527 * Creates a builder for a custom PatternLayout. 528 * 529 * @return a PatternLayout builder. 530 */ 531 @PluginBuilderFactory 532 public static Builder newBuilder() { 533 return new Builder(); 534 } 535 536 /** 537 * Custom PatternLayout builder. Use the {@link PatternLayout#newBuilder() builder factory method} to create this. 538 */ 539 public static class Builder implements org.apache.logging.log4j.core.util.Builder<PatternLayout> { 540 541 @PluginBuilderAttribute 542 private String pattern = PatternLayout.DEFAULT_CONVERSION_PATTERN; 543 544 @PluginElement("PatternSelector") 545 private PatternSelector patternSelector; 546 547 @PluginConfiguration 548 private Configuration configuration; 549 550 @PluginElement("Replace") 551 private RegexReplacement regexReplacement; 552 553 // LOG4J2-783 use platform default by default 554 @PluginBuilderAttribute 555 private Charset charset = Charset.defaultCharset(); 556 557 @PluginBuilderAttribute 558 private boolean alwaysWriteExceptions = true; 559 560 @PluginBuilderAttribute 561 private boolean disableAnsi = !useAnsiEscapeCodes(); 562 563 @PluginBuilderAttribute 564 private boolean noConsoleNoAnsi; 565 566 @PluginBuilderAttribute 567 private String header; 568 569 @PluginBuilderAttribute 570 private String footer; 571 572 private Builder() { 573 } 574 575 private boolean useAnsiEscapeCodes() { 576 final PropertiesUtil propertiesUtil = PropertiesUtil.getProperties(); 577 final boolean isPlatformSupportsAnsi = !propertiesUtil.isOsWindows(); 578 final boolean isJansiRequested = !propertiesUtil.getBooleanProperty("log4j.skipJansi", true); 579 return isPlatformSupportsAnsi || isJansiRequested; 580 } 581 582 /** 583 * @param pattern 584 * The pattern. If not specified, defaults to DEFAULT_CONVERSION_PATTERN. 585 */ 586 public Builder withPattern(final String pattern) { 587 this.pattern = pattern; 588 return this; 589 } 590 591 /** 592 * @param patternSelector 593 * Allows different patterns to be used based on some selection criteria. 594 */ 595 public Builder withPatternSelector(final PatternSelector patternSelector) { 596 this.patternSelector = patternSelector; 597 return this; 598 } 599 600 /** 601 * @param configuration 602 * The Configuration. Some Converters require access to the Interpolator. 603 */ 604 public Builder withConfiguration(final Configuration configuration) { 605 this.configuration = configuration; 606 return this; 607 } 608 609 /** 610 * @param regexReplacement 611 * A Regex replacement 612 */ 613 public Builder withRegexReplacement(final RegexReplacement regexReplacement) { 614 this.regexReplacement = regexReplacement; 615 return this; 616 } 617 618 /** 619 * @param charset 620 * The character set. The platform default is used if not specified. 621 */ 622 public Builder withCharset(final Charset charset) { 623 // LOG4J2-783 if null, use platform default by default 624 if (charset != null) { 625 this.charset = charset; 626 } 627 return this; 628 } 629 630 /** 631 * @param alwaysWriteExceptions 632 * If {@code "true"} (default) exceptions are always written even if the pattern contains no exception tokens. 633 */ 634 public Builder withAlwaysWriteExceptions(final boolean alwaysWriteExceptions) { 635 this.alwaysWriteExceptions = alwaysWriteExceptions; 636 return this; 637 } 638 639 /** 640 * @param disableAnsi 641 * If {@code "true"} (default is value of system property `log4j.skipJansi`, or `true` if undefined), 642 * do not output ANSI escape codes 643 */ 644 public Builder withDisableAnsi(final boolean disableAnsi) { 645 this.disableAnsi = disableAnsi; 646 return this; 647 } 648 649 /** 650 * @param noConsoleNoAnsi 651 * If {@code "true"} (default is false) and {@link System#console()} is null, do not output ANSI escape codes 652 */ 653 public Builder withNoConsoleNoAnsi(final boolean noConsoleNoAnsi) { 654 this.noConsoleNoAnsi = noConsoleNoAnsi; 655 return this; 656 } 657 658 /** 659 * @param header 660 * The footer to place at the top of the document, once. 661 */ 662 public Builder withHeader(final String header) { 663 this.header = header; 664 return this; 665 } 666 667 /** 668 * @param footer 669 * The footer to place at the bottom of the document, once. 670 */ 671 public Builder withFooter(final String footer) { 672 this.footer = footer; 673 return this; 674 } 675 676 @Override 677 public PatternLayout build() { 678 // fall back to DefaultConfiguration 679 if (configuration == null) { 680 configuration = new DefaultConfiguration(); 681 } 682 return new PatternLayout(configuration, regexReplacement, pattern, patternSelector, charset, 683 alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi, header, footer); 684 } 685 } 686 687 public Serializer getEventSerializer() { 688 return eventSerializer; 689 } 690}