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.HashMap;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.Map;
26
27 import org.apache.logging.log4j.Logger;
28 import org.apache.logging.log4j.core.config.Configuration;
29 import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
30 import org.apache.logging.log4j.core.config.plugins.util.PluginType;
31 import org.apache.logging.log4j.status.StatusLogger;
32 import org.apache.logging.log4j.util.Strings;
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();
130 final Map<String, PluginType<?>> plugins = manager.getPlugins();
131 final Map<String, Class<PatternConverter>> converters = new HashMap<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 converters.put(key, clazz);
144 }
145 }
146 } catch (final Exception ex) {
147 LOGGER.error("Error processing plugin " + type.getElementName(), ex);
148 }
149 }
150 converterRules = converters;
151 }
152
153 public List<PatternFormatter> parse(final String pattern) {
154 return parse(pattern, false, false);
155 }
156
157 public List<PatternFormatter> parse(final String pattern, final boolean alwaysWriteExceptions,
158 final boolean noConsoleNoAnsi) {
159 final List<PatternFormatter> list = new ArrayList<PatternFormatter>();
160 final List<PatternConverter> converters = new ArrayList<PatternConverter>();
161 final List<FormattingInfo> fields = new ArrayList<FormattingInfo>();
162
163 parse(pattern, converters, fields, noConsoleNoAnsi);
164
165 final Iterator<FormattingInfo> fieldIter = fields.iterator();
166 boolean handlesThrowable = false;
167
168 for (final PatternConverter converter : converters) {
169 LogEventPatternConverter pc;
170 if (converter instanceof LogEventPatternConverter) {
171 pc = (LogEventPatternConverter) converter;
172 handlesThrowable |= pc.handlesThrowable();
173 } else {
174 pc = new LiteralPatternConverter(config, Strings.EMPTY);
175 }
176
177 FormattingInfo field;
178 if (fieldIter.hasNext()) {
179 field = fieldIter.next();
180 } else {
181 field = FormattingInfo.getDefault();
182 }
183 list.add(new PatternFormatter(pc, field));
184 }
185 if (alwaysWriteExceptions && !handlesThrowable) {
186 final LogEventPatternConverter pc = ExtendedThrowablePatternConverter.newInstance(null);
187 list.add(new PatternFormatter(pc, FormattingInfo.getDefault()));
188 }
189 return list;
190 }
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213 private static int extractConverter(final char lastChar, final String pattern, int i, final StringBuilder convBuf,
214 final StringBuilder currentLiteral) {
215 convBuf.setLength(0);
216
217
218
219
220
221
222 if (!Character.isUnicodeIdentifierStart(lastChar)) {
223 return i;
224 }
225
226 convBuf.append(lastChar);
227
228 while (i < pattern.length() && Character.isUnicodeIdentifierPart(pattern.charAt(i))) {
229 convBuf.append(pattern.charAt(i));
230 currentLiteral.append(pattern.charAt(i));
231 i++;
232 }
233
234 return i;
235 }
236
237
238
239
240
241
242
243
244
245
246
247
248 private static int extractOptions(final String pattern, int i, final List<String> options) {
249 while (i < pattern.length() && pattern.charAt(i) == '{') {
250 final int begin = i++;
251 int end;
252 int depth = 0;
253 do {
254 end = pattern.indexOf('}', i);
255 if (end == -1) {
256 break;
257 } else {
258 final int next = pattern.indexOf("{", i);
259 if (next != -1 && next < end) {
260 i = end + 1;
261 ++depth;
262 } else if (depth > 0) {
263 --depth;
264 }
265 }
266 } while (depth > 0);
267
268 if (end == -1) {
269 break;
270 }
271
272 final String r = pattern.substring(begin + 1, end);
273 options.add(r);
274 i = end + 1;
275 }
276
277 return i;
278 }
279
280
281
282
283
284
285
286
287
288
289
290
291
292 public void parse(final String pattern, final List<PatternConverter> patternConverters,
293 final List<FormattingInfo> formattingInfos, final boolean noConsoleNoAnsi) {
294 if (pattern == null) {
295 throw new NullPointerException("pattern");
296 }
297
298 final StringBuilder currentLiteral = new StringBuilder(BUF_SIZE);
299
300 final int patternLength = pattern.length();
301 ParserState state = ParserState.LITERAL_STATE;
302 char c;
303 int i = 0;
304 FormattingInfo formattingInfo = FormattingInfo.getDefault();
305
306 while (i < patternLength) {
307 c = pattern.charAt(i++);
308
309 switch (state) {
310 case LITERAL_STATE:
311
312
313 if (i == patternLength) {
314 currentLiteral.append(c);
315
316 continue;
317 }
318
319 if (c == ESCAPE_CHAR) {
320
321 switch (pattern.charAt(i)) {
322 case ESCAPE_CHAR:
323 currentLiteral.append(c);
324 i++;
325
326 break;
327
328 default:
329
330 if (currentLiteral.length() != 0) {
331 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString()));
332 formattingInfos.add(FormattingInfo.getDefault());
333 }
334
335 currentLiteral.setLength(0);
336 currentLiteral.append(c);
337 state = ParserState.CONVERTER_STATE;
338 formattingInfo = FormattingInfo.getDefault();
339 }
340 } else {
341 currentLiteral.append(c);
342 }
343
344 break;
345
346 case CONVERTER_STATE:
347 currentLiteral.append(c);
348
349 switch (c) {
350 case '-':
351 formattingInfo = new FormattingInfo(true, formattingInfo.getMinLength(),
352 formattingInfo.getMaxLength());
353 break;
354
355 case '.':
356 state = ParserState.DOT_STATE;
357 break;
358
359 default:
360
361 if (c >= '0' && c <= '9') {
362 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), c - '0',
363 formattingInfo.getMaxLength());
364 state = ParserState.MIN_STATE;
365 } else {
366 i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules,
367 patternConverters, formattingInfos, noConsoleNoAnsi);
368
369
370 state = ParserState.LITERAL_STATE;
371 formattingInfo = FormattingInfo.getDefault();
372 currentLiteral.setLength(0);
373 }
374 }
375
376 break;
377
378 case MIN_STATE:
379 currentLiteral.append(c);
380
381 if (c >= '0' && c <= '9') {
382
383 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength()
384 * DECIMAL + c - '0', formattingInfo.getMaxLength());
385 } else if (c == '.') {
386 state = ParserState.DOT_STATE;
387 } else {
388 i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules,
389 patternConverters, formattingInfos, noConsoleNoAnsi);
390 state = ParserState.LITERAL_STATE;
391 formattingInfo = FormattingInfo.getDefault();
392 currentLiteral.setLength(0);
393 }
394
395 break;
396
397 case DOT_STATE:
398 currentLiteral.append(c);
399
400 if (c >= '0' && c <= '9') {
401 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
402 c - '0');
403 state = ParserState.MAX_STATE;
404 } else {
405 LOGGER.error("Error occurred in position " + i + ".\n Was expecting digit, instead got char \"" + c
406 + "\".");
407
408 state = ParserState.LITERAL_STATE;
409 }
410
411 break;
412
413 case MAX_STATE:
414 currentLiteral.append(c);
415
416 if (c >= '0' && c <= '9') {
417
418 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
419 formattingInfo.getMaxLength() * DECIMAL + c - '0');
420 } else {
421 i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules,
422 patternConverters, formattingInfos, noConsoleNoAnsi);
423 state = ParserState.LITERAL_STATE;
424 formattingInfo = FormattingInfo.getDefault();
425 currentLiteral.setLength(0);
426 }
427
428 break;
429 }
430 }
431
432
433 if (currentLiteral.length() != 0) {
434 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString()));
435 formattingInfos.add(FormattingInfo.getDefault());
436 }
437 }
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454 private PatternConverter createConverter(final String converterId, final StringBuilder currentLiteral,
455 final Map<String, Class<PatternConverter>> rules, final List<String> options, final boolean noConsoleNoAnsi) {
456 String converterName = converterId;
457 Class<PatternConverter> converterClass = null;
458
459 if (rules == null) {
460 LOGGER.error("Null rules for [" + converterId + ']');
461 return null;
462 }
463 for (int i = converterId.length(); i > 0 && converterClass == null; i--) {
464 converterName = converterName.substring(0, i);
465 converterClass = rules.get(converterName);
466 }
467
468 if (converterClass == null) {
469 LOGGER.error("Unrecognized format specifier [" + converterId + ']');
470 return null;
471 }
472
473 if (AnsiConverter.class.isAssignableFrom(converterClass)) {
474 options.add(NO_CONSOLE_NO_ANSI + '=' + noConsoleNoAnsi);
475 }
476
477
478 final Method[] methods = converterClass.getDeclaredMethods();
479 Method newInstanceMethod = null;
480 for (final Method method : methods) {
481 if (Modifier.isStatic(method.getModifiers()) && method.getDeclaringClass().equals(converterClass)
482 && method.getName().equals("newInstance")) {
483 if (newInstanceMethod == null) {
484 newInstanceMethod = method;
485 } else if (method.getReturnType().equals(newInstanceMethod.getReturnType())) {
486 LOGGER.error("Class " + converterClass + " cannot contain multiple static newInstance methods");
487 return null;
488 }
489 }
490 }
491 if (newInstanceMethod == null) {
492 LOGGER.error("Class " + converterClass + " does not contain a static newInstance method");
493 return null;
494 }
495
496 final Class<?>[] parmTypes = newInstanceMethod.getParameterTypes();
497 final Object[] parms = parmTypes.length > 0 ? new Object[parmTypes.length] : null;
498
499 if (parms != null) {
500 int i = 0;
501 boolean errors = false;
502 for (final Class<?> clazz : parmTypes) {
503 if (clazz.isArray() && clazz.getName().equals("[Ljava.lang.String;")) {
504 final String[] optionsArray = options.toArray(new String[options.size()]);
505 parms[i] = optionsArray;
506 } else if (clazz.isAssignableFrom(Configuration.class)) {
507 parms[i] = config;
508 } else {
509 LOGGER.error("Unknown parameter type " + clazz.getName() + " for static newInstance method of "
510 + converterClass.getName());
511 errors = true;
512 }
513 ++i;
514 }
515 if (errors) {
516 return null;
517 }
518 }
519
520 try {
521 final Object newObj = newInstanceMethod.invoke(null, parms);
522
523 if (newObj instanceof PatternConverter) {
524 currentLiteral.delete(0, currentLiteral.length() - (converterId.length() - converterName.length()));
525
526 return (PatternConverter) newObj;
527 }
528 LOGGER.warn("Class " + converterClass.getName() + " does not extend PatternConverter.");
529 } catch (final Exception ex) {
530 LOGGER.error("Error creating converter for " + converterId, ex);
531 }
532
533 return null;
534 }
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559 private int finalizeConverter(final char c, final String pattern, int i, final StringBuilder currentLiteral,
560 final FormattingInfo formattingInfo, final Map<String, Class<PatternConverter>> rules,
561 final List<PatternConverter> patternConverters, final List<FormattingInfo> formattingInfos,
562 final boolean noConsoleNoAnsi) {
563 final StringBuilder convBuf = new StringBuilder();
564 i = extractConverter(c, pattern, i, convBuf, currentLiteral);
565
566 final String converterId = convBuf.toString();
567
568 final List<String> options = new ArrayList<String>();
569 i = extractOptions(pattern, i, options);
570
571 final PatternConverter pc = createConverter(converterId, currentLiteral, rules, options, noConsoleNoAnsi);
572
573 if (pc == null) {
574 StringBuilder msg;
575
576 if (Strings.isEmpty(converterId)) {
577 msg = new StringBuilder("Empty conversion specifier starting at position ");
578 } else {
579 msg = new StringBuilder("Unrecognized conversion specifier [");
580 msg.append(converterId);
581 msg.append("] starting at position ");
582 }
583
584 msg.append(Integer.toString(i));
585 msg.append(" in conversion pattern.");
586
587 LOGGER.error(msg.toString());
588
589 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString()));
590 formattingInfos.add(FormattingInfo.getDefault());
591 } else {
592 patternConverters.add(pc);
593 formattingInfos.add(formattingInfo);
594
595 if (currentLiteral.length() > 0) {
596 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString()));
597 formattingInfos.add(FormattingInfo.getDefault());
598 }
599 }
600
601 currentLiteral.setLength(0);
602
603 return i;
604 }
605 }