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