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 final int next = pattern.indexOf("{", i);
257 if (next != -1 && next < end) {
258 i = end + 1;
259 ++depth;
260 } else if (depth > 0) {
261 --depth;
262 }
263 }
264 } while (depth > 0);
265
266 if (end == -1) {
267 break;
268 }
269
270 final String r = pattern.substring(begin + 1, end);
271 options.add(r);
272 i = end + 1;
273 }
274
275 return i;
276 }
277
278
279
280
281
282
283
284
285
286
287
288
289
290 public void parse(final String pattern, final List<PatternConverter> patternConverters,
291 final List<FormattingInfo> formattingInfos, final boolean noConsoleNoAnsi) {
292 if (pattern == null) {
293 throw new NullPointerException("pattern");
294 }
295
296 final StringBuilder currentLiteral = new StringBuilder(BUF_SIZE);
297
298 final int patternLength = pattern.length();
299 ParserState state = ParserState.LITERAL_STATE;
300 char c;
301 int i = 0;
302 FormattingInfo formattingInfo = FormattingInfo.getDefault();
303
304 while (i < patternLength) {
305 c = pattern.charAt(i++);
306
307 switch (state) {
308 case LITERAL_STATE:
309
310
311 if (i == patternLength) {
312 currentLiteral.append(c);
313
314 continue;
315 }
316
317 if (c == ESCAPE_CHAR) {
318
319 switch (pattern.charAt(i)) {
320 case ESCAPE_CHAR:
321 currentLiteral.append(c);
322 i++;
323
324 break;
325
326 default:
327
328 if (currentLiteral.length() != 0) {
329 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString()));
330 formattingInfos.add(FormattingInfo.getDefault());
331 }
332
333 currentLiteral.setLength(0);
334 currentLiteral.append(c);
335 state = ParserState.CONVERTER_STATE;
336 formattingInfo = FormattingInfo.getDefault();
337 }
338 } else {
339 currentLiteral.append(c);
340 }
341
342 break;
343
344 case CONVERTER_STATE:
345 currentLiteral.append(c);
346
347 switch (c) {
348 case '-':
349 formattingInfo = new FormattingInfo(true, formattingInfo.getMinLength(),
350 formattingInfo.getMaxLength());
351 break;
352
353 case '.':
354 state = ParserState.DOT_STATE;
355 break;
356
357 default:
358
359 if (c >= '0' && c <= '9') {
360 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), c - '0',
361 formattingInfo.getMaxLength());
362 state = ParserState.MIN_STATE;
363 } else {
364 i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules,
365 patternConverters, formattingInfos, noConsoleNoAnsi);
366
367
368 state = ParserState.LITERAL_STATE;
369 formattingInfo = FormattingInfo.getDefault();
370 currentLiteral.setLength(0);
371 }
372 }
373
374 break;
375
376 case MIN_STATE:
377 currentLiteral.append(c);
378
379 if (c >= '0' && c <= '9') {
380
381 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength()
382 * DECIMAL + c - '0', formattingInfo.getMaxLength());
383 } else if (c == '.') {
384 state = ParserState.DOT_STATE;
385 } else {
386 i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules,
387 patternConverters, formattingInfos, noConsoleNoAnsi);
388 state = ParserState.LITERAL_STATE;
389 formattingInfo = FormattingInfo.getDefault();
390 currentLiteral.setLength(0);
391 }
392
393 break;
394
395 case DOT_STATE:
396 currentLiteral.append(c);
397
398 if (c >= '0' && c <= '9') {
399 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
400 c - '0');
401 state = ParserState.MAX_STATE;
402 } else {
403 LOGGER.error("Error occurred in position " + i + ".\n Was expecting digit, instead got char \"" + c
404 + "\".");
405
406 state = ParserState.LITERAL_STATE;
407 }
408
409 break;
410
411 case MAX_STATE:
412 currentLiteral.append(c);
413
414 if (c >= '0' && c <= '9') {
415
416 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
417 formattingInfo.getMaxLength() * DECIMAL + c - '0');
418 } else {
419 i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules,
420 patternConverters, formattingInfos, noConsoleNoAnsi);
421 state = ParserState.LITERAL_STATE;
422 formattingInfo = FormattingInfo.getDefault();
423 currentLiteral.setLength(0);
424 }
425
426 break;
427 }
428 }
429
430
431 if (currentLiteral.length() != 0) {
432 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString()));
433 formattingInfos.add(FormattingInfo.getDefault());
434 }
435 }
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452 private PatternConverter createConverter(final String converterId, final StringBuilder currentLiteral,
453 final Map<String, Class<PatternConverter>> rules, final List<String> options, final boolean noConsoleNoAnsi) {
454 String converterName = converterId;
455 Class<PatternConverter> converterClass = null;
456
457 if (rules == null) {
458 LOGGER.error("Null rules for [" + converterId + ']');
459 return null;
460 }
461 for (int i = converterId.length(); i > 0 && converterClass == null; i--) {
462 converterName = converterName.substring(0, i);
463 converterClass = rules.get(converterName);
464 }
465
466 if (converterClass == null) {
467 LOGGER.error("Unrecognized format specifier [" + converterId + ']');
468 return null;
469 }
470
471 if (AnsiConverter.class.isAssignableFrom(converterClass)) {
472 options.add(NO_CONSOLE_NO_ANSI + '=' + noConsoleNoAnsi);
473 }
474
475
476 final Method[] methods = converterClass.getDeclaredMethods();
477 Method newInstanceMethod = null;
478 for (final Method method : methods) {
479 if (Modifier.isStatic(method.getModifiers()) && method.getDeclaringClass().equals(converterClass)
480 && method.getName().equals("newInstance")) {
481 if (newInstanceMethod == null) {
482 newInstanceMethod = method;
483 } else if (method.getReturnType().equals(newInstanceMethod.getReturnType())) {
484 LOGGER.error("Class " + converterClass + " cannot contain multiple static newInstance methods");
485 return null;
486 }
487 }
488 }
489 if (newInstanceMethod == null) {
490 LOGGER.error("Class " + converterClass + " does not contain a static newInstance method");
491 return null;
492 }
493
494 final Class<?>[] parmTypes = newInstanceMethod.getParameterTypes();
495 final Object[] parms = parmTypes.length > 0 ? new Object[parmTypes.length] : null;
496
497 if (parms != null) {
498 int i = 0;
499 boolean errors = false;
500 for (final Class<?> clazz : parmTypes) {
501 if (clazz.isArray() && clazz.getName().equals("[Ljava.lang.String;")) {
502 final String[] optionsArray = options.toArray(new String[options.size()]);
503 parms[i] = optionsArray;
504 } else if (clazz.isAssignableFrom(Configuration.class)) {
505 parms[i] = config;
506 } else {
507 LOGGER.error("Unknown parameter type " + clazz.getName() + " for static newInstance method of "
508 + converterClass.getName());
509 errors = true;
510 }
511 ++i;
512 }
513 if (errors) {
514 return null;
515 }
516 }
517
518 try {
519 final Object newObj = newInstanceMethod.invoke(null, parms);
520
521 if (newObj instanceof PatternConverter) {
522 currentLiteral.delete(0, currentLiteral.length() - (converterId.length() - converterName.length()));
523
524 return (PatternConverter) newObj;
525 }
526 LOGGER.warn("Class " + converterClass.getName() + " does not extend PatternConverter.");
527 } catch (final Exception ex) {
528 LOGGER.error("Error creating converter for " + converterId, ex);
529 }
530
531 return null;
532 }
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557 private int finalizeConverter(final char c, final String pattern, int i, final StringBuilder currentLiteral,
558 final FormattingInfo formattingInfo, final Map<String, Class<PatternConverter>> rules,
559 final List<PatternConverter> patternConverters, final List<FormattingInfo> formattingInfos,
560 final boolean noConsoleNoAnsi) {
561 final StringBuilder convBuf = new StringBuilder();
562 i = extractConverter(c, pattern, i, convBuf, currentLiteral);
563
564 final String converterId = convBuf.toString();
565
566 final List<String> options = new ArrayList<String>();
567 i = extractOptions(pattern, i, options);
568
569 final PatternConverter pc = createConverter(converterId, currentLiteral, rules, options, noConsoleNoAnsi);
570
571 if (pc == null) {
572 StringBuilder msg;
573
574 if (Strings.isEmpty(converterId)) {
575 msg = new StringBuilder("Empty conversion specifier starting at position ");
576 } else {
577 msg = new StringBuilder("Unrecognized conversion specifier [");
578 msg.append(converterId);
579 msg.append("] starting at position ");
580 }
581
582 msg.append(Integer.toString(i));
583 msg.append(" in conversion pattern.");
584
585 LOGGER.error(msg.toString());
586
587 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString()));
588 formattingInfos.add(FormattingInfo.getDefault());
589 } else {
590 patternConverters.add(pc);
591 formattingInfos.add(formattingInfo);
592
593 if (currentLiteral.length() > 0) {
594 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString()));
595 formattingInfos.add(FormattingInfo.getDefault());
596 }
597 }
598
599 currentLiteral.setLength(0);
600
601 return i;
602 }
603 }