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