1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.layout;
18
19 import java.nio.charset.Charset;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23
24 import org.apache.logging.log4j.core.Layout;
25 import org.apache.logging.log4j.core.LogEvent;
26 import org.apache.logging.log4j.core.config.Configuration;
27 import org.apache.logging.log4j.core.config.DefaultConfiguration;
28 import org.apache.logging.log4j.core.config.Node;
29 import org.apache.logging.log4j.core.config.plugins.Plugin;
30 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
31 import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
32 import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
33 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
34 import org.apache.logging.log4j.core.config.plugins.PluginElement;
35 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
36 import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
37 import org.apache.logging.log4j.core.pattern.PatternFormatter;
38 import org.apache.logging.log4j.core.pattern.PatternParser;
39 import org.apache.logging.log4j.core.pattern.RegexReplacement;
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 @Plugin(name = "PatternLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
56 public final class PatternLayout extends AbstractStringLayout {
57
58 private static final long serialVersionUID = 1L;
59
60
61
62
63
64
65 public static final String DEFAULT_CONVERSION_PATTERN = "%m%n";
66
67
68
69
70
71 public static final String TTCC_CONVERSION_PATTERN =
72 "%r [%t] %p %c %x - %m%n";
73
74
75
76
77
78 public static final String SIMPLE_CONVERSION_PATTERN =
79 "%d [%t] %p %c - %m%n";
80
81
82 public static final String KEY = "Converter";
83
84
85
86
87 private final PatternFormatter[] formatters;
88
89 private static ThreadLocal<StringBuilder> strBuilder = new ThreadLocal<StringBuilder>() {
90 @Override
91 protected StringBuilder initialValue() {
92 return new StringBuilder(1024);
93 }
94 };
95
96
97
98
99 private final String conversionPattern;
100
101
102
103
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
115
116
117
118
119
120
121
122
123
124
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
164
165
166
167 public String getConversionPattern() {
168 return conversionPattern;
169 }
170
171
172
173
174
175
176
177
178
179
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
193
194
195
196
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
215
216
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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
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
281
282
283
284
285
286 public static PatternLayout createDefaultLayout() {
287 return newBuilder().build();
288 }
289
290
291
292
293
294 @PluginBuilderFactory
295 public static Builder newBuilder() {
296 return new Builder();
297 }
298
299
300
301
302 public static class Builder implements org.apache.logging.log4j.core.util.Builder<PatternLayout> {
303
304
305
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
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
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
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 }