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