1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.log4j.helpers;
18
19 import org.apache.log4j.helpers.LogLog;
20 import org.apache.log4j.helpers.OptionConverter;
21 import org.apache.log4j.helpers.AbsoluteTimeDateFormat;
22 import org.apache.log4j.Layout;
23 import org.apache.log4j.spi.LoggingEvent;
24 import org.apache.log4j.spi.LocationInfo;
25 import java.text.DateFormat;
26 import java.text.SimpleDateFormat;
27 import java.util.Date;
28
29
30
31
32
33 /***
34 Most of the work of the {@link org.apache.log4j.PatternLayout} class
35 is delegated to the PatternParser class.
36
37 <p>It is this class that parses conversion patterns and creates
38 a chained list of {@link OptionConverter OptionConverters}.
39
40 @author <a href=mailto:"cakalijp@Maritz.com">James P. Cakalic</a>
41 @author Ceki Gülcü
42 @author Anders Kristensen
43
44 @since 0.8.2
45 */
46 public class PatternParser {
47
48 private static final char ESCAPE_CHAR = '%';
49
50 private static final int LITERAL_STATE = 0;
51 private static final int CONVERTER_STATE = 1;
52 private static final int DOT_STATE = 3;
53 private static final int MIN_STATE = 4;
54 private static final int MAX_STATE = 5;
55
56 static final int FULL_LOCATION_CONVERTER = 1000;
57 static final int METHOD_LOCATION_CONVERTER = 1001;
58 static final int CLASS_LOCATION_CONVERTER = 1002;
59 static final int LINE_LOCATION_CONVERTER = 1003;
60 static final int FILE_LOCATION_CONVERTER = 1004;
61
62 static final int RELATIVE_TIME_CONVERTER = 2000;
63 static final int THREAD_CONVERTER = 2001;
64 static final int LEVEL_CONVERTER = 2002;
65 static final int NDC_CONVERTER = 2003;
66 static final int MESSAGE_CONVERTER = 2004;
67
68 int state;
69 protected StringBuffer currentLiteral = new StringBuffer(32);
70 protected int patternLength;
71 protected int i;
72 PatternConverter head;
73 PatternConverter tail;
74 protected FormattingInfo formattingInfo = new FormattingInfo();
75 protected String pattern;
76
77 public
78 PatternParser(String pattern) {
79 this.pattern = pattern;
80 patternLength = pattern.length();
81 state = LITERAL_STATE;
82 }
83
84 private
85 void addToList(PatternConverter pc) {
86 if(head == null) {
87 head = tail = pc;
88 } else {
89 tail.next = pc;
90 tail = pc;
91 }
92 }
93
94 protected
95 String extractOption() {
96 if((i < patternLength) && (pattern.charAt(i) == '{')) {
97 int end = pattern.indexOf('}', i);
98 if (end > i) {
99 String r = pattern.substring(i + 1, end);
100 i = end+1;
101 return r;
102 }
103 }
104 return null;
105 }
106
107
108 /***
109 The option is expected to be in decimal and positive. In case of
110 error, zero is returned. */
111 protected
112 int extractPrecisionOption() {
113 String opt = extractOption();
114 int r = 0;
115 if(opt != null) {
116 try {
117 r = Integer.parseInt(opt);
118 if(r <= 0) {
119 LogLog.error(
120 "Precision option (" + opt + ") isn't a positive integer.");
121 r = 0;
122 }
123 }
124 catch (NumberFormatException e) {
125 LogLog.error("Category option \""+opt+"\" not a decimal integer.", e);
126 }
127 }
128 return r;
129 }
130
131 public
132 PatternConverter parse() {
133 char c;
134 i = 0;
135 while(i < patternLength) {
136 c = pattern.charAt(i++);
137 switch(state) {
138 case LITERAL_STATE:
139
140 if(i == patternLength) {
141 currentLiteral.append(c);
142 continue;
143 }
144 if(c == ESCAPE_CHAR) {
145
146 switch(pattern.charAt(i)) {
147 case ESCAPE_CHAR:
148 currentLiteral.append(c);
149 i++;
150 break;
151 case 'n':
152 currentLiteral.append(Layout.LINE_SEP);
153 i++;
154 break;
155 default:
156 if(currentLiteral.length() != 0) {
157 addToList(new LiteralPatternConverter(
158 currentLiteral.toString()));
159
160
161 }
162 currentLiteral.setLength(0);
163 currentLiteral.append(c);
164 state = CONVERTER_STATE;
165 formattingInfo.reset();
166 }
167 }
168 else {
169 currentLiteral.append(c);
170 }
171 break;
172 case CONVERTER_STATE:
173 currentLiteral.append(c);
174 switch(c) {
175 case '-':
176 formattingInfo.leftAlign = true;
177 break;
178 case '.':
179 state = DOT_STATE;
180 break;
181 default:
182 if(c >= '0' && c <= '9') {
183 formattingInfo.min = c - '0';
184 state = MIN_STATE;
185 }
186 else
187 finalizeConverter(c);
188 }
189 break;
190 case MIN_STATE:
191 currentLiteral.append(c);
192 if(c >= '0' && c <= '9')
193 formattingInfo.min = formattingInfo.min*10 + (c - '0');
194 else if(c == '.')
195 state = DOT_STATE;
196 else {
197 finalizeConverter(c);
198 }
199 break;
200 case DOT_STATE:
201 currentLiteral.append(c);
202 if(c >= '0' && c <= '9') {
203 formattingInfo.max = c - '0';
204 state = MAX_STATE;
205 }
206 else {
207 LogLog.error("Error occured in position "+i
208 +".\n Was expecting digit, instead got char \""+c+"\".");
209 state = LITERAL_STATE;
210 }
211 break;
212 case MAX_STATE:
213 currentLiteral.append(c);
214 if(c >= '0' && c <= '9')
215 formattingInfo.max = formattingInfo.max*10 + (c - '0');
216 else {
217 finalizeConverter(c);
218 state = LITERAL_STATE;
219 }
220 break;
221 }
222 }
223 if(currentLiteral.length() != 0) {
224 addToList(new LiteralPatternConverter(currentLiteral.toString()));
225
226 }
227 return head;
228 }
229
230 protected
231 void finalizeConverter(char c) {
232 PatternConverter pc = null;
233 switch(c) {
234 case 'c':
235 pc = new CategoryPatternConverter(formattingInfo,
236 extractPrecisionOption());
237
238
239 currentLiteral.setLength(0);
240 break;
241 case 'C':
242 pc = new ClassNamePatternConverter(formattingInfo,
243 extractPrecisionOption());
244
245
246 currentLiteral.setLength(0);
247 break;
248 case 'd':
249 String dateFormatStr = AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT;
250 DateFormat df;
251 String dOpt = extractOption();
252 if(dOpt != null)
253 dateFormatStr = dOpt;
254
255 if(dateFormatStr.equalsIgnoreCase(
256 AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT))
257 df = new ISO8601DateFormat();
258 else if(dateFormatStr.equalsIgnoreCase(
259 AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT))
260 df = new AbsoluteTimeDateFormat();
261 else if(dateFormatStr.equalsIgnoreCase(
262 AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT))
263 df = new DateTimeDateFormat();
264 else {
265 try {
266 df = new SimpleDateFormat(dateFormatStr);
267 }
268 catch (IllegalArgumentException e) {
269 LogLog.error("Could not instantiate SimpleDateFormat with " +
270 dateFormatStr, e);
271 df = (DateFormat) OptionConverter.instantiateByClassName(
272 "org.apache.log4j.helpers.ISO8601DateFormat",
273 DateFormat.class, null);
274 }
275 }
276 pc = new DatePatternConverter(formattingInfo, df);
277
278
279 currentLiteral.setLength(0);
280 break;
281 case 'F':
282 pc = new LocationPatternConverter(formattingInfo,
283 FILE_LOCATION_CONVERTER);
284
285
286 currentLiteral.setLength(0);
287 break;
288 case 'l':
289 pc = new LocationPatternConverter(formattingInfo,
290 FULL_LOCATION_CONVERTER);
291
292
293 currentLiteral.setLength(0);
294 break;
295 case 'L':
296 pc = new LocationPatternConverter(formattingInfo,
297 LINE_LOCATION_CONVERTER);
298
299
300 currentLiteral.setLength(0);
301 break;
302 case 'm':
303 pc = new BasicPatternConverter(formattingInfo, MESSAGE_CONVERTER);
304
305
306 currentLiteral.setLength(0);
307 break;
308 case 'M':
309 pc = new LocationPatternConverter(formattingInfo,
310 METHOD_LOCATION_CONVERTER);
311
312
313 currentLiteral.setLength(0);
314 break;
315 case 'p':
316 pc = new BasicPatternConverter(formattingInfo, LEVEL_CONVERTER);
317
318
319 currentLiteral.setLength(0);
320 break;
321 case 'r':
322 pc = new BasicPatternConverter(formattingInfo,
323 RELATIVE_TIME_CONVERTER);
324
325
326 currentLiteral.setLength(0);
327 break;
328 case 't':
329 pc = new BasicPatternConverter(formattingInfo, THREAD_CONVERTER);
330
331
332 currentLiteral.setLength(0);
333 break;
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348 case 'x':
349 pc = new BasicPatternConverter(formattingInfo, NDC_CONVERTER);
350
351 currentLiteral.setLength(0);
352 break;
353 case 'X':
354 String xOpt = extractOption();
355 pc = new MDCPatternConverter(formattingInfo, xOpt);
356 currentLiteral.setLength(0);
357 break;
358 default:
359 LogLog.error("Unexpected char [" +c+"] at position "+i
360 +" in conversion patterrn.");
361 pc = new LiteralPatternConverter(currentLiteral.toString());
362 currentLiteral.setLength(0);
363 }
364
365 addConverter(pc);
366 }
367
368 protected
369 void addConverter(PatternConverter pc) {
370 currentLiteral.setLength(0);
371
372 addToList(pc);
373
374 state = LITERAL_STATE;
375
376 formattingInfo.reset();
377 }
378
379
380
381
382
383 private static class BasicPatternConverter extends PatternConverter {
384 int type;
385
386 BasicPatternConverter(FormattingInfo formattingInfo, int type) {
387 super(formattingInfo);
388 this.type = type;
389 }
390
391 public
392 String convert(LoggingEvent event) {
393 switch(type) {
394 case RELATIVE_TIME_CONVERTER:
395 return (Long.toString(event.timeStamp - LoggingEvent.getStartTime()));
396 case THREAD_CONVERTER:
397 return event.getThreadName();
398 case LEVEL_CONVERTER:
399 return event.getLevel().toString();
400 case NDC_CONVERTER:
401 return event.getNDC();
402 case MESSAGE_CONVERTER: {
403 return event.getRenderedMessage();
404 }
405 default: return null;
406 }
407 }
408 }
409
410 private static class LiteralPatternConverter extends PatternConverter {
411 private String literal;
412
413 LiteralPatternConverter(String value) {
414 literal = value;
415 }
416
417 public
418 final
419 void format(StringBuffer sbuf, LoggingEvent event) {
420 sbuf.append(literal);
421 }
422
423 public
424 String convert(LoggingEvent event) {
425 return literal;
426 }
427 }
428
429 private static class DatePatternConverter extends PatternConverter {
430 private DateFormat df;
431 private Date date;
432
433 DatePatternConverter(FormattingInfo formattingInfo, DateFormat df) {
434 super(formattingInfo);
435 date = new Date();
436 this.df = df;
437 }
438
439 public
440 String convert(LoggingEvent event) {
441 date.setTime(event.timeStamp);
442 String converted = null;
443 try {
444 converted = df.format(date);
445 }
446 catch (Exception ex) {
447 LogLog.error("Error occured while converting date.", ex);
448 }
449 return converted;
450 }
451 }
452
453 private static class MDCPatternConverter extends PatternConverter {
454 private String key;
455
456 MDCPatternConverter(FormattingInfo formattingInfo, String key) {
457 super(formattingInfo);
458 this.key = key;
459 }
460
461 public
462 String convert(LoggingEvent event) {
463 Object val = event.getMDC(key);
464 if(val == null) {
465 return null;
466 } else {
467 return val.toString();
468 }
469 }
470 }
471
472
473 private class LocationPatternConverter extends PatternConverter {
474 int type;
475
476 LocationPatternConverter(FormattingInfo formattingInfo, int type) {
477 super(formattingInfo);
478 this.type = type;
479 }
480
481 public
482 String convert(LoggingEvent event) {
483 LocationInfo locationInfo = event.getLocationInformation();
484 switch(type) {
485 case FULL_LOCATION_CONVERTER:
486 return locationInfo.fullInfo;
487 case METHOD_LOCATION_CONVERTER:
488 return locationInfo.getMethodName();
489 case LINE_LOCATION_CONVERTER:
490 return locationInfo.getLineNumber();
491 case FILE_LOCATION_CONVERTER:
492 return locationInfo.getFileName();
493 default: return null;
494 }
495 }
496 }
497
498 private static abstract class NamedPatternConverter extends PatternConverter {
499 int precision;
500
501 NamedPatternConverter(FormattingInfo formattingInfo, int precision) {
502 super(formattingInfo);
503 this.precision = precision;
504 }
505
506 abstract
507 String getFullyQualifiedName(LoggingEvent event);
508
509 public
510 String convert(LoggingEvent event) {
511 String n = getFullyQualifiedName(event);
512 if(precision <= 0)
513 return n;
514 else {
515 int len = n.length();
516
517
518
519
520 int end = len -1 ;
521 for(int i = precision; i > 0; i--) {
522 end = n.lastIndexOf('.', end-1);
523 if(end == -1)
524 return n;
525 }
526 return n.substring(end+1, len);
527 }
528 }
529 }
530
531 private class ClassNamePatternConverter extends NamedPatternConverter {
532
533 ClassNamePatternConverter(FormattingInfo formattingInfo, int precision) {
534 super(formattingInfo, precision);
535 }
536
537 String getFullyQualifiedName(LoggingEvent event) {
538 return event.getLocationInformation().getClassName();
539 }
540 }
541
542 private class CategoryPatternConverter extends NamedPatternConverter {
543
544 CategoryPatternConverter(FormattingInfo formattingInfo, int precision) {
545 super(formattingInfo, precision);
546 }
547
548 String getFullyQualifiedName(LoggingEvent event) {
549 return event.getLoggerName();
550 }
551 }
552 }
553