001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.log4j.helpers; 018 019import org.apache.log4j.Layout; 020import org.apache.log4j.spi.LoggingEvent; 021import org.apache.log4j.spi.LocationInfo; 022import java.text.DateFormat; 023import java.text.SimpleDateFormat; 024import java.util.Date; 025import java.util.Map; 026import java.util.Arrays; 027 028// Contributors: Nelson Minar <(nelson@monkey.org> 029// Igor E. Poteryaev <jah@mail.ru> 030// Reinhard Deschler <reinhard.deschler@web.de> 031 032/** 033 Most of the work of the {@link org.apache.log4j.PatternLayout} class 034 is delegated to the PatternParser class. 035 036 <p>It is this class that parses conversion patterns and creates 037 a chained list of {@link OptionConverter OptionConverters}. 038 039 @author <a href=mailto:"cakalijp@Maritz.com">James P. Cakalic</a> 040 @author Ceki Gülcü 041 @author Anders Kristensen 042 043 @since 0.8.2 044*/ 045public class PatternParser { 046 047 private static final char ESCAPE_CHAR = '%'; 048 049 private static final int LITERAL_STATE = 0; 050 private static final int CONVERTER_STATE = 1; 051 private static final int DOT_STATE = 3; 052 private static final int MIN_STATE = 4; 053 private static final int MAX_STATE = 5; 054 055 static final int FULL_LOCATION_CONVERTER = 1000; 056 static final int METHOD_LOCATION_CONVERTER = 1001; 057 static final int CLASS_LOCATION_CONVERTER = 1002; 058 static final int LINE_LOCATION_CONVERTER = 1003; 059 static final int FILE_LOCATION_CONVERTER = 1004; 060 061 static final int RELATIVE_TIME_CONVERTER = 2000; 062 static final int THREAD_CONVERTER = 2001; 063 static final int LEVEL_CONVERTER = 2002; 064 static final int NDC_CONVERTER = 2003; 065 static final int MESSAGE_CONVERTER = 2004; 066 067 int state; 068 protected StringBuffer currentLiteral = new StringBuffer(32); 069 protected int patternLength; 070 protected int i; 071 PatternConverter head; 072 PatternConverter tail; 073 protected FormattingInfo formattingInfo = new FormattingInfo(); 074 protected String pattern; 075 076 public 077 PatternParser(String pattern) { 078 this.pattern = pattern; 079 patternLength = pattern.length(); 080 state = LITERAL_STATE; 081 } 082 083 private 084 void addToList(PatternConverter pc) { 085 if(head == null) { 086 head = tail = pc; 087 } else { 088 tail.next = pc; 089 tail = pc; 090 } 091 } 092 093 protected 094 String extractOption() { 095 if((i < patternLength) && (pattern.charAt(i) == '{')) { 096 int end = pattern.indexOf('}', i); 097 if (end > i) { 098 String r = pattern.substring(i + 1, end); 099 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