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.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  // Contributors:   Nelson Minar <(nelson@monkey.org>
30  //                 Igor E. Poteryaev <jah@mail.ru>
31  //                 Reinhard Deschler <reinhard.deschler@web.de>
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&uuml;lc&uuml;
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         // In literal state, the last char is always a literal.
140         if(i == patternLength) {
141           currentLiteral.append(c);
142           continue;
143         }
144         if(c == ESCAPE_CHAR) {
145           // peek at the next char.
146           switch(pattern.charAt(i)) {
147           case ESCAPE_CHAR:
148             currentLiteral.append(c);
149             i++; // move pointer
150             break;
151           case 'n':
152             currentLiteral.append(Layout.LINE_SEP);
153             i++; // move pointer
154             break;
155           default:
156             if(currentLiteral.length() != 0) {
157               addToList(new LiteralPatternConverter(
158                                                   currentLiteral.toString()));
159               //LogLog.debug("Parsed LITERAL converter: \""
160               //           +currentLiteral+"\".");
161             }
162             currentLiteral.setLength(0);
163             currentLiteral.append(c); // append %
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 	} // switch
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       } // switch
222     } // while
223     if(currentLiteral.length() != 0) {
224       addToList(new LiteralPatternConverter(currentLiteral.toString()));
225       //LogLog.debug("Parsed LITERAL converter: \""+currentLiteral+"\".");
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       //LogLog.debug("CATEGORY converter.");
238       //formattingInfo.dump();
239       currentLiteral.setLength(0);
240       break;
241     case 'C':
242       pc = new ClassNamePatternConverter(formattingInfo,
243 					 extractPrecisionOption());
244       //LogLog.debug("CLASS_NAME converter.");
245       //formattingInfo.dump();
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       //LogLog.debug("DATE converter {"+dateFormatStr+"}.");
278       //formattingInfo.dump();
279       currentLiteral.setLength(0);
280       break;
281     case 'F':
282       pc = new LocationPatternConverter(formattingInfo,
283 					FILE_LOCATION_CONVERTER);
284       //LogLog.debug("File name converter.");
285       //formattingInfo.dump();
286       currentLiteral.setLength(0);
287       break;
288     case 'l':
289       pc = new LocationPatternConverter(formattingInfo,
290 					FULL_LOCATION_CONVERTER);
291       //LogLog.debug("Location converter.");
292       //formattingInfo.dump();
293       currentLiteral.setLength(0);
294       break;
295     case 'L':
296       pc = new LocationPatternConverter(formattingInfo,
297 					LINE_LOCATION_CONVERTER);
298       //LogLog.debug("LINE NUMBER converter.");
299       //formattingInfo.dump();
300       currentLiteral.setLength(0);
301       break;
302     case 'm':
303       pc = new BasicPatternConverter(formattingInfo, MESSAGE_CONVERTER);
304       //LogLog.debug("MESSAGE converter.");
305       //formattingInfo.dump();
306       currentLiteral.setLength(0);
307       break;
308     case 'M':
309       pc = new LocationPatternConverter(formattingInfo,
310 					METHOD_LOCATION_CONVERTER);
311       //LogLog.debug("METHOD converter.");
312       //formattingInfo.dump();
313       currentLiteral.setLength(0);
314       break;
315     case 'p':
316       pc = new BasicPatternConverter(formattingInfo, LEVEL_CONVERTER);
317       //LogLog.debug("LEVEL converter.");
318       //formattingInfo.dump();
319       currentLiteral.setLength(0);
320       break;
321     case 'r':
322       pc = new BasicPatternConverter(formattingInfo,
323 					 RELATIVE_TIME_CONVERTER);
324       //LogLog.debug("RELATIVE time converter.");
325       //formattingInfo.dump();
326       currentLiteral.setLength(0);
327       break;
328     case 't':
329       pc = new BasicPatternConverter(formattingInfo, THREAD_CONVERTER);
330       //LogLog.debug("THREAD converter.");
331       //formattingInfo.dump();
332       currentLiteral.setLength(0);
333       break;
334       /*case 'u':
335       if(i < patternLength) {
336 	char cNext = pattern.charAt(i);
337 	if(cNext >= '0' && cNext <= '9') {
338 	  pc = new UserFieldPatternConverter(formattingInfo, cNext - '0');
339 	  LogLog.debug("USER converter ["+cNext+"].");
340 	  formattingInfo.dump();
341 	  currentLiteral.setLength(0);
342 	  i++;
343 	}
344 	else
345 	  LogLog.error("Unexpected char" +cNext+" at position "+i);
346       }
347       break;*/
348     case 'x':
349       pc = new BasicPatternConverter(formattingInfo, NDC_CONVERTER);
350       //LogLog.debug("NDC converter.");
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     // Add the pattern converter to the list.
372     addToList(pc);
373     // Next pattern is assumed to be a literal.
374     state = LITERAL_STATE;
375     // Reset formatting info
376     formattingInfo.reset();
377   }
378 
379   // ---------------------------------------------------------------------
380   //                      PatternConverters
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 	// We substract 1 from 'len' when assigning to 'end' to avoid out of
518 	// bounds exception in return r.substring(end+1, len). This can happen if
519 	// precision is 1 and the category name ends with a dot.
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