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.NanoClockFactory;
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 NanoClockFactory.setMode(NanoClockFactory.Mode.Dummy);
177 for (final PatternConverter converter : converters) {
178 if (converter instanceof NanoTimePatternConverter) {
179
180
181 NanoClockFactory.setMode(NanoClockFactory.Mode.System);
182 }
183 LogEventPatternConverter pc;
184 if (converter instanceof LogEventPatternConverter) {
185 pc = (LogEventPatternConverter) converter;
186 handlesThrowable |= pc.handlesThrowable();
187 } else {
188 pc = new LiteralPatternConverter(config, Strings.EMPTY, true);
189 }
190
191 FormattingInfo field;
192 if (fieldIter.hasNext()) {
193 field = fieldIter.next();
194 } else {
195 field = FormattingInfo.getDefault();
196 }
197 list.add(new PatternFormatter(pc, field));
198 }
199 if (alwaysWriteExceptions && !handlesThrowable) {
200 final LogEventPatternConverter pc = ExtendedThrowablePatternConverter.newInstance(null);
201 list.add(new PatternFormatter(pc, FormattingInfo.getDefault()));
202 }
203 return list;
204 }
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 private static int extractConverter(final char lastChar, final String pattern, final int start,
230 final StringBuilder convBuf, final StringBuilder currentLiteral) {
231 int i = start;
232 convBuf.setLength(0);
233
234
235
236
237
238
239 if (!Character.isUnicodeIdentifierStart(lastChar)) {
240 return i;
241 }
242
243 convBuf.append(lastChar);
244
245 while (i < pattern.length() && Character.isUnicodeIdentifierPart(pattern.charAt(i))) {
246 convBuf.append(pattern.charAt(i));
247 currentLiteral.append(pattern.charAt(i));
248 i++;
249 }
250
251 return i;
252 }
253
254
255
256
257
258
259
260
261
262
263
264
265 private static int extractOptions(final String pattern, final int start, final List<String> options) {
266 int i = start;
267 while (i < pattern.length() && pattern.charAt(i) == '{') {
268 final int begin = i++;
269 int end;
270 int depth = 0;
271 do {
272 end = pattern.indexOf('}', i);
273 if (end == -1) {
274 break;
275 }
276 final int next = pattern.indexOf("{", i);
277 if (next != -1 && next < end) {
278 i = end + 1;
279 ++depth;
280 } else if (depth > 0) {
281 --depth;
282 }
283 } while (depth > 0);
284
285 if (end == -1) {
286 break;
287 }
288
289 final String r = pattern.substring(begin + 1, end);
290 options.add(r);
291 i = end + 1;
292 }
293
294 return i;
295 }
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311 public void parse(final String pattern, final List<PatternConverter> patternConverters,
312 final List<FormattingInfo> formattingInfos, final boolean noConsoleNoAnsi,
313 final boolean convertBackslashes) {
314 Objects.requireNonNull(pattern, "pattern");
315
316 final StringBuilder currentLiteral = new StringBuilder(BUF_SIZE);
317
318 final int patternLength = pattern.length();
319 ParserState state = ParserState.LITERAL_STATE;
320 char c;
321 int i = 0;
322 FormattingInfo formattingInfo = FormattingInfo.getDefault();
323
324 while (i < patternLength) {
325 c = pattern.charAt(i++);
326
327 switch (state) {
328 case LITERAL_STATE:
329
330
331 if (i == patternLength) {
332 currentLiteral.append(c);
333
334 continue;
335 }
336
337 if (c == ESCAPE_CHAR) {
338
339 switch (pattern.charAt(i)) {
340 case ESCAPE_CHAR:
341 currentLiteral.append(c);
342 i++;
343
344 break;
345
346 default:
347
348 if (currentLiteral.length() != 0) {
349 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString(),
350 convertBackslashes));
351 formattingInfos.add(FormattingInfo.getDefault());
352 }
353
354 currentLiteral.setLength(0);
355 currentLiteral.append(c);
356 state = ParserState.CONVERTER_STATE;
357 formattingInfo = FormattingInfo.getDefault();
358 }
359 } else {
360 currentLiteral.append(c);
361 }
362
363 break;
364
365 case CONVERTER_STATE:
366 currentLiteral.append(c);
367
368 switch (c) {
369 case '-':
370 formattingInfo = new FormattingInfo(true, formattingInfo.getMinLength(),
371 formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate());
372 break;
373
374 case '.':
375 state = ParserState.DOT_STATE;
376 break;
377
378 default:
379
380 if (c >= '0' && c <= '9') {
381 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), c - '0',
382 formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate());
383 state = ParserState.MIN_STATE;
384 } else {
385 i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules,
386 patternConverters, formattingInfos, noConsoleNoAnsi, convertBackslashes);
387
388
389 state = ParserState.LITERAL_STATE;
390 formattingInfo = FormattingInfo.getDefault();
391 currentLiteral.setLength(0);
392 }
393 }
394
395 break;
396
397 case MIN_STATE:
398 currentLiteral.append(c);
399
400 if (c >= '0' && c <= '9') {
401
402 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength()
403 * DECIMAL + c - '0', formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate());
404 } else if (c == '.') {
405 state = ParserState.DOT_STATE;
406 } else {
407 i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules,
408 patternConverters, formattingInfos, noConsoleNoAnsi, convertBackslashes);
409 state = ParserState.LITERAL_STATE;
410 formattingInfo = FormattingInfo.getDefault();
411 currentLiteral.setLength(0);
412 }
413
414 break;
415
416 case DOT_STATE:
417 currentLiteral.append(c);
418 switch (c) {
419 case '-':
420 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
421 formattingInfo.getMaxLength(),false);
422 break;
423
424 default:
425
426 if (c >= '0' && c <= '9') {
427 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
428 c - '0', formattingInfo.isLeftTruncate());
429 state = ParserState.MAX_STATE;
430 } else {
431 LOGGER.error("Error occurred in position " + i + ".\n Was expecting digit, instead got char \"" + c
432 + "\".");
433
434 state = ParserState.LITERAL_STATE;
435 }
436 }
437
438 break;
439
440 case MAX_STATE:
441 currentLiteral.append(c);
442
443 if (c >= '0' && c <= '9') {
444
445 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
446 formattingInfo.getMaxLength() * DECIMAL + c - '0', formattingInfo.isLeftTruncate());
447 } else {
448 i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules,
449 patternConverters, formattingInfos, noConsoleNoAnsi, convertBackslashes);
450 state = ParserState.LITERAL_STATE;
451 formattingInfo = FormattingInfo.getDefault();
452 currentLiteral.setLength(0);
453 }
454
455 break;
456 }
457 }
458
459
460 if (currentLiteral.length() != 0) {
461 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString(), convertBackslashes));
462 formattingInfos.add(FormattingInfo.getDefault());
463 }
464 }
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481 private PatternConverter createConverter(final String converterId, final StringBuilder currentLiteral,
482 final Map<String, Class<PatternConverter>> rules, final List<String> options, final boolean noConsoleNoAnsi) {
483 String converterName = converterId;
484 Class<PatternConverter> converterClass = null;
485
486 if (rules == null) {
487 LOGGER.error("Null rules for [" + converterId + ']');
488 return null;
489 }
490 for (int i = converterId.length(); i > 0 && converterClass == null; i--) {
491 converterName = converterName.substring(0, i);
492 converterClass = rules.get(converterName);
493 }
494
495 if (converterClass == null) {
496 LOGGER.error("Unrecognized format specifier [" + converterId + ']');
497 return null;
498 }
499
500 if (AnsiConverter.class.isAssignableFrom(converterClass)) {
501 options.add(NO_CONSOLE_NO_ANSI + '=' + noConsoleNoAnsi);
502 }
503
504
505 final Method[] methods = converterClass.getDeclaredMethods();
506 Method newInstanceMethod = null;
507 for (final Method method : methods) {
508 if (Modifier.isStatic(method.getModifiers()) && method.getDeclaringClass().equals(converterClass)
509 && method.getName().equals("newInstance")) {
510 if (newInstanceMethod == null) {
511 newInstanceMethod = method;
512 } else if (method.getReturnType().equals(newInstanceMethod.getReturnType())) {
513 LOGGER.error("Class " + converterClass + " cannot contain multiple static newInstance methods");
514 return null;
515 }
516 }
517 }
518 if (newInstanceMethod == null) {
519 LOGGER.error("Class " + converterClass + " does not contain a static newInstance method");
520 return null;
521 }
522
523 final Class<?>[] parmTypes = newInstanceMethod.getParameterTypes();
524 final Object[] parms = parmTypes.length > 0 ? new Object[parmTypes.length] : null;
525
526 if (parms != null) {
527 int i = 0;
528 boolean errors = false;
529 for (final Class<?> clazz : parmTypes) {
530 if (clazz.isArray() && clazz.getName().equals("[Ljava.lang.String;")) {
531 final String[] optionsArray = options.toArray(new String[options.size()]);
532 parms[i] = optionsArray;
533 } else if (clazz.isAssignableFrom(Configuration.class)) {
534 parms[i] = config;
535 } else {
536 LOGGER.error("Unknown parameter type " + clazz.getName() + " for static newInstance method of "
537 + converterClass.getName());
538 errors = true;
539 }
540 ++i;
541 }
542 if (errors) {
543 return null;
544 }
545 }
546
547 try {
548 final Object newObj = newInstanceMethod.invoke(null, parms);
549
550 if (newObj instanceof PatternConverter) {
551 currentLiteral.delete(0, currentLiteral.length() - (converterId.length() - converterName.length()));
552
553 return (PatternConverter) newObj;
554 }
555 LOGGER.warn("Class {} does not extend PatternConverter.", converterClass.getName());
556 } catch (final Exception ex) {
557 LOGGER.error("Error creating converter for " + converterId, ex);
558 }
559
560 return null;
561 }
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 private int finalizeConverter(final char c, final String pattern, final int start,
589 final StringBuilder currentLiteral, final FormattingInfo formattingInfo,
590 final Map<String, Class<PatternConverter>> rules, final List<PatternConverter> patternConverters,
591 final List<FormattingInfo> formattingInfos, final boolean noConsoleNoAnsi, final boolean convertBackslashes) {
592 int i = start;
593 final StringBuilder convBuf = new StringBuilder();
594 i = extractConverter(c, pattern, i, convBuf, currentLiteral);
595
596 final String converterId = convBuf.toString();
597
598 final List<String> options = new ArrayList<>();
599 i = extractOptions(pattern, i, options);
600
601 final PatternConverter pc = createConverter(converterId, currentLiteral, rules, options, noConsoleNoAnsi);
602
603 if (pc == null) {
604 StringBuilder msg;
605
606 if (Strings.isEmpty(converterId)) {
607 msg = new StringBuilder("Empty conversion specifier starting at position ");
608 } else {
609 msg = new StringBuilder("Unrecognized conversion specifier [");
610 msg.append(converterId);
611 msg.append("] starting at position ");
612 }
613
614 msg.append(Integer.toString(i));
615 msg.append(" in conversion pattern.");
616
617 LOGGER.error(msg.toString());
618
619 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString(), convertBackslashes));
620 formattingInfos.add(FormattingInfo.getDefault());
621 } else {
622 patternConverters.add(pc);
623 formattingInfos.add(formattingInfo);
624
625 if (currentLiteral.length() > 0) {
626 patternConverters
627 .add(new LiteralPatternConverter(config, currentLiteral.toString(), convertBackslashes));
628 formattingInfos.add(FormattingInfo.getDefault());
629 }
630 }
631
632 currentLiteral.setLength(0);
633
634 return i;
635 }
636 }