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());
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());
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());
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
413 if (c >= '0' && c <= '9') {
414 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
415 c - '0');
416 state = ParserState.MAX_STATE;
417 } else {
418 LOGGER.error("Error occurred in position " + i + ".\n Was expecting digit, instead got char \"" + c
419 + "\".");
420
421 state = ParserState.LITERAL_STATE;
422 }
423
424 break;
425
426 case MAX_STATE:
427 currentLiteral.append(c);
428
429 if (c >= '0' && c <= '9') {
430
431 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
432 formattingInfo.getMaxLength() * DECIMAL + c - '0');
433 } else {
434 i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules,
435 patternConverters, formattingInfos, noConsoleNoAnsi, convertBackslashes);
436 state = ParserState.LITERAL_STATE;
437 formattingInfo = FormattingInfo.getDefault();
438 currentLiteral.setLength(0);
439 }
440
441 break;
442 }
443 }
444
445
446 if (currentLiteral.length() != 0) {
447 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString(), convertBackslashes));
448 formattingInfos.add(FormattingInfo.getDefault());
449 }
450 }
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467 private PatternConverter createConverter(final String converterId, final StringBuilder currentLiteral,
468 final Map<String, Class<PatternConverter>> rules, final List<String> options, final boolean noConsoleNoAnsi) {
469 String converterName = converterId;
470 Class<PatternConverter> converterClass = null;
471
472 if (rules == null) {
473 LOGGER.error("Null rules for [" + converterId + ']');
474 return null;
475 }
476 for (int i = converterId.length(); i > 0 && converterClass == null; i--) {
477 converterName = converterName.substring(0, i);
478 converterClass = rules.get(converterName);
479 }
480
481 if (converterClass == null) {
482 LOGGER.error("Unrecognized format specifier [" + converterId + ']');
483 return null;
484 }
485
486 if (AnsiConverter.class.isAssignableFrom(converterClass)) {
487 options.add(NO_CONSOLE_NO_ANSI + '=' + noConsoleNoAnsi);
488 }
489
490
491 final Method[] methods = converterClass.getDeclaredMethods();
492 Method newInstanceMethod = null;
493 for (final Method method : methods) {
494 if (Modifier.isStatic(method.getModifiers()) && method.getDeclaringClass().equals(converterClass)
495 && method.getName().equals("newInstance")) {
496 if (newInstanceMethod == null) {
497 newInstanceMethod = method;
498 } else if (method.getReturnType().equals(newInstanceMethod.getReturnType())) {
499 LOGGER.error("Class " + converterClass + " cannot contain multiple static newInstance methods");
500 return null;
501 }
502 }
503 }
504 if (newInstanceMethod == null) {
505 LOGGER.error("Class " + converterClass + " does not contain a static newInstance method");
506 return null;
507 }
508
509 final Class<?>[] parmTypes = newInstanceMethod.getParameterTypes();
510 final Object[] parms = parmTypes.length > 0 ? new Object[parmTypes.length] : null;
511
512 if (parms != null) {
513 int i = 0;
514 boolean errors = false;
515 for (final Class<?> clazz : parmTypes) {
516 if (clazz.isArray() && clazz.getName().equals("[Ljava.lang.String;")) {
517 final String[] optionsArray = options.toArray(new String[options.size()]);
518 parms[i] = optionsArray;
519 } else if (clazz.isAssignableFrom(Configuration.class)) {
520 parms[i] = config;
521 } else {
522 LOGGER.error("Unknown parameter type " + clazz.getName() + " for static newInstance method of "
523 + converterClass.getName());
524 errors = true;
525 }
526 ++i;
527 }
528 if (errors) {
529 return null;
530 }
531 }
532
533 try {
534 final Object newObj = newInstanceMethod.invoke(null, parms);
535
536 if (newObj instanceof PatternConverter) {
537 currentLiteral.delete(0, currentLiteral.length() - (converterId.length() - converterName.length()));
538
539 return (PatternConverter) newObj;
540 }
541 LOGGER.warn("Class " + converterClass.getName() + " does not extend PatternConverter.");
542 } catch (final Exception ex) {
543 LOGGER.error("Error creating converter for " + converterId, ex);
544 }
545
546 return null;
547 }
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574 private int finalizeConverter(final char c, final String pattern, final int start,
575 final StringBuilder currentLiteral, final FormattingInfo formattingInfo,
576 final Map<String, Class<PatternConverter>> rules, final List<PatternConverter> patternConverters,
577 final List<FormattingInfo> formattingInfos, final boolean noConsoleNoAnsi, final boolean convertBackslashes) {
578 int i = start;
579 final StringBuilder convBuf = new StringBuilder();
580 i = extractConverter(c, pattern, i, convBuf, currentLiteral);
581
582 final String converterId = convBuf.toString();
583
584 final List<String> options = new ArrayList<String>();
585 i = extractOptions(pattern, i, options);
586
587 final PatternConverter pc = createConverter(converterId, currentLiteral, rules, options, noConsoleNoAnsi);
588
589 if (pc == null) {
590 StringBuilder msg;
591
592 if (Strings.isEmpty(converterId)) {
593 msg = new StringBuilder("Empty conversion specifier starting at position ");
594 } else {
595 msg = new StringBuilder("Unrecognized conversion specifier [");
596 msg.append(converterId);
597 msg.append("] starting at position ");
598 }
599
600 msg.append(Integer.toString(i));
601 msg.append(" in conversion pattern.");
602
603 LOGGER.error(msg.toString());
604
605 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString(), convertBackslashes));
606 formattingInfos.add(FormattingInfo.getDefault());
607 } else {
608 patternConverters.add(pc);
609 formattingInfos.add(formattingInfo);
610
611 if (currentLiteral.length() > 0) {
612 patternConverters
613 .add(new LiteralPatternConverter(config, currentLiteral.toString(), convertBackslashes));
614 formattingInfos.add(FormattingInfo.getDefault());
615 }
616 }
617
618 currentLiteral.setLength(0);
619
620 return i;
621 }
622 }