1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.pattern;
18
19 import org.apache.logging.log4j.Logger;
20 import org.apache.logging.log4j.core.config.Configuration;
21 import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
22 import org.apache.logging.log4j.core.config.plugins.util.PluginType;
23 import org.apache.logging.log4j.status.StatusLogger;
24 import org.apache.logging.log4j.util.Strings;
25
26 import java.lang.reflect.Method;
27 import java.lang.reflect.Modifier;
28 import java.util.ArrayList;
29 import java.util.Iterator;
30 import java.util.LinkedHashMap;
31 import java.util.List;
32 import java.util.Map;
33
34
35
36
37
38
39
40
41 public final class PatternParser {
42 static final String NO_CONSOLE_NO_ANSI = "noConsoleNoAnsi";
43
44
45
46
47 private static final char ESCAPE_CHAR = '%';
48
49
50
51
52 private enum ParserState {
53
54
55
56 LITERAL_STATE,
57
58
59
60
61 CONVERTER_STATE,
62
63
64
65
66 DOT_STATE,
67
68
69
70
71 MIN_STATE,
72
73
74
75
76 MAX_STATE;
77 }
78
79 private static final Logger LOGGER = StatusLogger.getLogger();
80
81 private static final int BUF_SIZE = 32;
82
83 private static final int DECIMAL = 10;
84
85 private final Configuration config;
86
87 private final Map<String, Class<PatternConverter>> converterRules;
88
89
90
91
92
93
94
95 public PatternParser(final String converterKey) {
96 this(null, converterKey, null, null);
97 }
98
99
100
101
102
103
104
105
106
107
108
109 public PatternParser(final Configuration config, final String converterKey, final Class<?> expected) {
110 this(config, converterKey, expected, null);
111 }
112
113
114
115
116
117
118
119
120
121
122
123
124
125 public PatternParser(final Configuration config, final String converterKey, final Class<?> expectedClass,
126 final Class<?> filterClass) {
127 this.config = config;
128 final PluginManager manager = new PluginManager(converterKey);
129 manager.collectPlugins(config == null ? null : config.getPluginPackages());
130 final Map<String, PluginType<?>> plugins = manager.getPlugins();
131 final Map<String, Class<PatternConverter>> converters = new LinkedHashMap<String, Class<PatternConverter>>();
132
133 for (final PluginType<?> type : plugins.values()) {
134 try {
135 @SuppressWarnings("unchecked")
136 final Class<PatternConverter> clazz = (Class<PatternConverter>) type.getPluginClass();
137 if (filterClass != null && !filterClass.isAssignableFrom(clazz)) {
138 continue;
139 }
140 final ConverterKeys keys = clazz.getAnnotation(ConverterKeys.class);
141 if (keys != null) {
142 for (final String key : keys.value()) {
143 if (converters.containsKey(key)) {
144 LOGGER.warn("Converter key '{}' is already mapped to '{}'. " +
145 "Sorry, Dave, I can't let you do that! Ignoring plugin [{}].",
146 key, converters.get(key), clazz);
147 } else {
148 converters.put(key, clazz);
149 }
150 }
151 }
152 } catch (final Exception ex) {
153 LOGGER.error("Error processing plugin " + type.getElementName(), ex);
154 }
155 }
156 converterRules = converters;
157 }
158
159 public List<PatternFormatter> parse(final String pattern) {
160 return parse(pattern, false, false);
161 }
162
163 public List<PatternFormatter> parse(final String pattern, final boolean alwaysWriteExceptions,
164 final boolean noConsoleNoAnsi) {
165 final List<PatternFormatter> list = new ArrayList<PatternFormatter>();
166 final List<PatternConverter> converters = new ArrayList<PatternConverter>();
167 final List<FormattingInfo> fields = new ArrayList<FormattingInfo>();
168
169 parse(pattern, converters, fields, noConsoleNoAnsi, true);
170
171 final Iterator<FormattingInfo> fieldIter = fields.iterator();
172 boolean handlesThrowable = false;
173
174 for (final PatternConverter converter : converters) {
175 LogEventPatternConverter pc;
176 if (converter instanceof LogEventPatternConverter) {
177 pc = (LogEventPatternConverter) converter;
178 handlesThrowable |= pc.handlesThrowable();
179 } else {
180 pc = new LiteralPatternConverter(config, Strings.EMPTY, true);
181 }
182
183 FormattingInfo field;
184 if (fieldIter.hasNext()) {
185 field = fieldIter.next();
186 } else {
187 field = FormattingInfo.getDefault();
188 }
189 list.add(new PatternFormatter(pc, field));
190 }
191 if (alwaysWriteExceptions && !handlesThrowable) {
192 final LogEventPatternConverter pc = ExtendedThrowablePatternConverter.newInstance(null);
193 list.add(new PatternFormatter(pc, FormattingInfo.getDefault()));
194 }
195 return list;
196 }
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221 private static int extractConverter(final char lastChar, final String pattern, final int start,
222 final StringBuilder convBuf, final StringBuilder currentLiteral) {
223 int i = start;
224 convBuf.setLength(0);
225
226
227
228
229
230
231 if (!Character.isUnicodeIdentifierStart(lastChar)) {
232 return i;
233 }
234
235 convBuf.append(lastChar);
236
237 while (i < pattern.length() && Character.isUnicodeIdentifierPart(pattern.charAt(i))) {
238 convBuf.append(pattern.charAt(i));
239 currentLiteral.append(pattern.charAt(i));
240 i++;
241 }
242
243 return i;
244 }
245
246
247
248
249
250
251
252
253
254
255
256
257 private static int extractOptions(final String pattern, final int start, final List<String> options) {
258 int i = start;
259 while (i < pattern.length() && pattern.charAt(i) == '{') {
260 final int begin = i++;
261 int end;
262 int depth = 0;
263 do {
264 end = pattern.indexOf('}', i);
265 if (end == -1) {
266 break;
267 }
268 final int next = pattern.indexOf("{", i);
269 if (next != -1 && next < end) {
270 i = end + 1;
271 ++depth;
272 } else if (depth > 0) {
273 --depth;
274 }
275 } while (depth > 0);
276
277 if (end == -1) {
278 break;
279 }
280
281 final String r = pattern.substring(begin + 1, end);
282 options.add(r);
283 i = end + 1;
284 }
285
286 return i;
287 }
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303 public void parse(final String pattern, final List<PatternConverter> patternConverters,
304 final List<FormattingInfo> formattingInfos, final boolean noConsoleNoAnsi,
305 final boolean convertBackslashes) {
306 if (pattern == null) {
307 throw new NullPointerException("pattern");
308 }
309
310 final StringBuilder currentLiteral = new StringBuilder(BUF_SIZE);
311
312 final int patternLength = pattern.length();
313 ParserState state = ParserState.LITERAL_STATE;
314 char c;
315 int i = 0;
316 FormattingInfo formattingInfo = FormattingInfo.getDefault();
317
318 while (i < patternLength) {
319 c = pattern.charAt(i++);
320
321 switch (state) {
322 case LITERAL_STATE:
323
324
325 if (i == patternLength) {
326 currentLiteral.append(c);
327
328 continue;
329 }
330
331 if (c == ESCAPE_CHAR) {
332
333 switch (pattern.charAt(i)) {
334 case ESCAPE_CHAR:
335 currentLiteral.append(c);
336 i++;
337
338 break;
339
340 default:
341
342 if (currentLiteral.length() != 0) {
343 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString(),
344 convertBackslashes));
345 formattingInfos.add(FormattingInfo.getDefault());
346 }
347
348 currentLiteral.setLength(0);
349 currentLiteral.append(c);
350 state = ParserState.CONVERTER_STATE;
351 formattingInfo = FormattingInfo.getDefault();
352 }
353 } else {
354 currentLiteral.append(c);
355 }
356
357 break;
358
359 case CONVERTER_STATE:
360 currentLiteral.append(c);
361
362 switch (c) {
363 case '-':
364 formattingInfo = new FormattingInfo(true, formattingInfo.getMinLength(),
365 formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate());
366 break;
367
368 case '.':
369 state = ParserState.DOT_STATE;
370 break;
371
372 default:
373
374 if (c >= '0' && c <= '9') {
375 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), c - '0',
376 formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate());
377 state = ParserState.MIN_STATE;
378 } else {
379 i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules,
380 patternConverters, formattingInfos, noConsoleNoAnsi, convertBackslashes);
381
382
383 state = ParserState.LITERAL_STATE;
384 formattingInfo = FormattingInfo.getDefault();
385 currentLiteral.setLength(0);
386 }
387 }
388
389 break;
390
391 case MIN_STATE:
392 currentLiteral.append(c);
393
394 if (c >= '0' && c <= '9') {
395
396 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength()
397 * DECIMAL + c - '0', formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate());
398 } else if (c == '.') {
399 state = ParserState.DOT_STATE;
400 } else {
401 i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules,
402 patternConverters, formattingInfos, noConsoleNoAnsi, convertBackslashes);
403 state = ParserState.LITERAL_STATE;
404 formattingInfo = FormattingInfo.getDefault();
405 currentLiteral.setLength(0);
406 }
407
408 break;
409
410 case DOT_STATE:
411 currentLiteral.append(c);
412 switch (c) {
413 case '-':
414 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
415 formattingInfo.getMaxLength(),false);
416 break;
417
418 default:
419
420 if (c >= '0' && c <= '9') {
421 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
422 c - '0', formattingInfo.isLeftTruncate());
423 state = ParserState.MAX_STATE;
424 } else {
425 LOGGER.error("Error occurred in position " + i + ".\n Was expecting digit, instead got char \"" + c
426 + "\".");
427
428 state = ParserState.LITERAL_STATE;
429 }
430 }
431
432 break;
433
434 case MAX_STATE:
435 currentLiteral.append(c);
436
437 if (c >= '0' && c <= '9') {
438
439 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
440 formattingInfo.getMaxLength() * DECIMAL + c - '0', formattingInfo.isLeftTruncate());
441 } else {
442 i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules,
443 patternConverters, formattingInfos, noConsoleNoAnsi, convertBackslashes);
444 state = ParserState.LITERAL_STATE;
445 formattingInfo = FormattingInfo.getDefault();
446 currentLiteral.setLength(0);
447 }
448
449 break;
450 }
451 }
452
453
454 if (currentLiteral.length() != 0) {
455 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString(), convertBackslashes));
456 formattingInfos.add(FormattingInfo.getDefault());
457 }
458 }
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475 private PatternConverter createConverter(final String converterId, final StringBuilder currentLiteral,
476 final Map<String, Class<PatternConverter>> rules, final List<String> options, final boolean noConsoleNoAnsi) {
477 String converterName = converterId;
478 Class<PatternConverter> converterClass = null;
479
480 if (rules == null) {
481 LOGGER.error("Null rules for [" + converterId + ']');
482 return null;
483 }
484 for (int i = converterId.length(); i > 0 && converterClass == null; i--) {
485 converterName = converterName.substring(0, i);
486 converterClass = rules.get(converterName);
487 }
488
489 if (converterClass == null) {
490 LOGGER.error("Unrecognized format specifier [" + converterId + ']');
491 return null;
492 }
493
494 if (AnsiConverter.class.isAssignableFrom(converterClass)) {
495 options.add(NO_CONSOLE_NO_ANSI + '=' + noConsoleNoAnsi);
496 }
497
498
499 final Method[] methods = converterClass.getDeclaredMethods();
500 Method newInstanceMethod = null;
501 for (final Method method : methods) {
502 if (Modifier.isStatic(method.getModifiers()) && method.getDeclaringClass().equals(converterClass)
503 && method.getName().equals("newInstance")) {
504 if (newInstanceMethod == null) {
505 newInstanceMethod = method;
506 } else if (method.getReturnType().equals(newInstanceMethod.getReturnType())) {
507 LOGGER.error("Class " + converterClass + " cannot contain multiple static newInstance methods");
508 return null;
509 }
510 }
511 }
512 if (newInstanceMethod == null) {
513 LOGGER.error("Class " + converterClass + " does not contain a static newInstance method");
514 return null;
515 }
516
517 final Class<?>[] parmTypes = newInstanceMethod.getParameterTypes();
518 final Object[] parms = parmTypes.length > 0 ? new Object[parmTypes.length] : null;
519
520 if (parms != null) {
521 int i = 0;
522 boolean errors = false;
523 for (final Class<?> clazz : parmTypes) {
524 if (clazz.isArray() && clazz.getName().equals("[Ljava.lang.String;")) {
525 final String[] optionsArray = options.toArray(new String[options.size()]);
526 parms[i] = optionsArray;
527 } else if (clazz.isAssignableFrom(Configuration.class)) {
528 parms[i] = config;
529 } else {
530 LOGGER.error("Unknown parameter type " + clazz.getName() + " for static newInstance method of "
531 + converterClass.getName());
532 errors = true;
533 }
534 ++i;
535 }
536 if (errors) {
537 return null;
538 }
539 }
540
541 try {
542 final Object newObj = newInstanceMethod.invoke(null, parms);
543
544 if (newObj instanceof PatternConverter) {
545 currentLiteral.delete(0, currentLiteral.length() - (converterId.length() - converterName.length()));
546
547 return (PatternConverter) newObj;
548 }
549 LOGGER.warn("Class {} does not extend PatternConverter.", converterClass.getName());
550 } catch (final Exception ex) {
551 LOGGER.error("Error creating converter for " + converterId, ex);
552 }
553
554 return null;
555 }
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582 private int finalizeConverter(final char c, final String pattern, final int start,
583 final StringBuilder currentLiteral, final FormattingInfo formattingInfo,
584 final Map<String, Class<PatternConverter>> rules, final List<PatternConverter> patternConverters,
585 final List<FormattingInfo> formattingInfos, final boolean noConsoleNoAnsi, final boolean convertBackslashes) {
586 int i = start;
587 final StringBuilder convBuf = new StringBuilder();
588 i = extractConverter(c, pattern, i, convBuf, currentLiteral);
589
590 final String converterId = convBuf.toString();
591
592 final List<String> options = new ArrayList<String>();
593 i = extractOptions(pattern, i, options);
594
595 final PatternConverter pc = createConverter(converterId, currentLiteral, rules, options, noConsoleNoAnsi);
596
597 if (pc == null) {
598 StringBuilder msg;
599
600 if (Strings.isEmpty(converterId)) {
601 msg = new StringBuilder("Empty conversion specifier starting at position ");
602 } else {
603 msg = new StringBuilder("Unrecognized conversion specifier [");
604 msg.append(converterId);
605 msg.append("] starting at position ");
606 }
607
608 msg.append(Integer.toString(i));
609 msg.append(" in conversion pattern.");
610
611 LOGGER.error(msg.toString());
612
613 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString(), convertBackslashes));
614 formattingInfos.add(FormattingInfo.getDefault());
615 } else {
616 patternConverters.add(pc);
617 formattingInfos.add(formattingInfo);
618
619 if (currentLiteral.length() > 0) {
620 patternConverters
621 .add(new LiteralPatternConverter(config, currentLiteral.toString(), convertBackslashes));
622 formattingInfos.add(FormattingInfo.getDefault());
623 }
624 }
625
626 currentLiteral.setLength(0);
627
628 return i;
629 }
630 }