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