View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
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  // Contributors:   Nelson Minar <(nelson@monkey.org>
29  //                 Igor E. Poteryaev <jah@mail.ru>
30  //                 Reinhard Deschler <reinhard.deschler@web.de>
31  
32  /**
33     Most of the work of the {@link org.apache.log4j.PatternLayout} class
34     is delegated to the PatternParser class.
35  
36     <p>It is this class that parses conversion patterns and creates
37     a chained list of {@link OptionConverter OptionConverters}.
38  
39     @author <a href=mailto:"cakalijp@Maritz.com">James P. Cakalic</a>
40     @author Ceki G&uuml;lc&uuml;
41     @author Anders Kristensen
42  
43     @since 0.8.2
44  */
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   /**
108      The option is expected to be in decimal and positive. In case of
109      error, zero is returned.  */
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         // In literal state, the last char is always a literal.
139         if(i == patternLength) {
140           currentLiteral.append(c);
141           continue;
142         }
143         if(c == ESCAPE_CHAR) {
144           // peek at the next char.
145           switch(pattern.charAt(i)) {
146           case ESCAPE_CHAR:
147             currentLiteral.append(c);
148             i++; // move pointer
149             break;
150           case 'n':
151             currentLiteral.append(Layout.LINE_SEP);
152             i++; // move pointer
153             break;
154           default:
155             if(currentLiteral.length() != 0) {
156               addToList(new LiteralPatternConverter(
157                                                   currentLiteral.toString()));
158               //LogLog.debug("Parsed LITERAL converter: \""
159               //           +currentLiteral+"\".");
160             }
161             currentLiteral.setLength(0);
162             currentLiteral.append(c); // append %
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 	} // switch
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       } // switch
221     } // while
222     if(currentLiteral.length() != 0) {
223       addToList(new LiteralPatternConverter(currentLiteral.toString()));
224       //LogLog.debug("Parsed LITERAL converter: \""+currentLiteral+"\".");
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       //LogLog.debug("CATEGORY converter.");
237       //formattingInfo.dump();
238       currentLiteral.setLength(0);
239       break;
240     case 'C':
241       pc = new ClassNamePatternConverter(formattingInfo,
242 					 extractPrecisionOption());
243       //LogLog.debug("CLASS_NAME converter.");
244       //formattingInfo.dump();
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       //LogLog.debug("DATE converter {"+dateFormatStr+"}.");
277       //formattingInfo.dump();
278       currentLiteral.setLength(0);
279       break;
280     case 'F':
281       pc = new LocationPatternConverter(formattingInfo,
282 					FILE_LOCATION_CONVERTER);
283       //LogLog.debug("File name converter.");
284       //formattingInfo.dump();
285       currentLiteral.setLength(0);
286       break;
287     case 'l':
288       pc = new LocationPatternConverter(formattingInfo,
289 					FULL_LOCATION_CONVERTER);
290       //LogLog.debug("Location converter.");
291       //formattingInfo.dump();
292       currentLiteral.setLength(0);
293       break;
294     case 'L':
295       pc = new LocationPatternConverter(formattingInfo,
296 					LINE_LOCATION_CONVERTER);
297       //LogLog.debug("LINE NUMBER converter.");
298       //formattingInfo.dump();
299       currentLiteral.setLength(0);
300       break;
301     case 'm':
302       pc = new BasicPatternConverter(formattingInfo, MESSAGE_CONVERTER);
303       //LogLog.debug("MESSAGE converter.");
304       //formattingInfo.dump();
305       currentLiteral.setLength(0);
306       break;
307     case 'M':
308       pc = new LocationPatternConverter(formattingInfo,
309 					METHOD_LOCATION_CONVERTER);
310       //LogLog.debug("METHOD converter.");
311       //formattingInfo.dump();
312       currentLiteral.setLength(0);
313       break;
314     case 'p':
315       pc = new BasicPatternConverter(formattingInfo, LEVEL_CONVERTER);
316       //LogLog.debug("LEVEL converter.");
317       //formattingInfo.dump();
318       currentLiteral.setLength(0);
319       break;
320     case 'r':
321       pc = new BasicPatternConverter(formattingInfo,
322 					 RELATIVE_TIME_CONVERTER);
323       //LogLog.debug("RELATIVE time converter.");
324       //formattingInfo.dump();
325       currentLiteral.setLength(0);
326       break;
327     case 't':
328       pc = new BasicPatternConverter(formattingInfo, THREAD_CONVERTER);
329       //LogLog.debug("THREAD converter.");
330       //formattingInfo.dump();
331       currentLiteral.setLength(0);
332       break;
333       /*case 'u':
334       if(i < patternLength) {
335 	char cNext = pattern.charAt(i);
336 	if(cNext >= '0' && cNext <= '9') {
337 	  pc = new UserFieldPatternConverter(formattingInfo, cNext - '0');
338 	  LogLog.debug("USER converter ["+cNext+"].");
339 	  formattingInfo.dump();
340 	  currentLiteral.setLength(0);
341 	  i++;
342 	}
343 	else
344 	  LogLog.error("Unexpected char" +cNext+" at position "+i);
345       }
346       break;*/
347     case 'x':
348       pc = new BasicPatternConverter(formattingInfo, NDC_CONVERTER);
349       //LogLog.debug("NDC converter.");
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     // Add the pattern converter to the list.
371     addToList(pc);
372     // Next pattern is assumed to be a literal.
373     state = LITERAL_STATE;
374     // Reset formatting info
375     formattingInfo.reset();
376   }
377 
378   // ---------------------------------------------------------------------
379   //                      PatternConverters
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 	// We substract 1 from 'len' when assigning to 'end' to avoid out of
535 	// bounds exception in return r.substring(end+1, len). This can happen if
536 	// precision is 1 and the category name ends with a dot.
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