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