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