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