001 package hirondelle.web4jtools.logview.parser; 002 003 import java.util.*; 004 import hirondelle.web4j.util.Util; 005 import java.util.logging.*; 006 import static hirondelle.web4j.util.Consts.NEW_LINE; 007 import static hirondelle.web4j.util.Consts.SPACE; 008 import static hirondelle.web4j.util.Consts.NOT_FOUND; 009 import java.text.SimpleDateFormat; 010 011 import hirondelle.web4jtools.logview.parsedview.LoggerRecord; 012 import hirondelle.web4jtools.logview.parsedview.ParsedCriteria; 013 014 /** 015 * Parse a JDK log file having default format. 016 * 017 * <P>Example of a typical log record : 018 * <PRE> 019 16-Sep-2011 9:00:28 PM hirondelle.web4j.webmaster.LoggingConfigImpl tryTestMessages 020 FINE: This is a test message for Logger 'hirondelle.web4jtools' 021 </PRE> 022 * 023 * The above record is parsed by this class into the following parts : 024 *<ul> 025 * <li>Date - <tt>16-Sep-2011 9:00:28 PM</tt> 026 * <li>Logger - <tt>hirondelle.web4j.webmaster.LoggingConfigImpl</tt> 027 * <li>Method - <tt>tryTestMessages</tt> 028 * <li>Level - <tt>FINE</tt> 029 * <li>Message - <tt>This is a test message for Logger 'hirondelle.web4jtools'</tt> 030 *</ul> 031 * 032 * <P>The message is often over multiple lines. 033 * 034 * <P>There is no specific delimiter for this format, so this implementation uses an 035 * <em>ad hoc</em> method of delimiting records. 036 * 037 * <P>Note that this class is package-private, to prevent it from being used directly by 038 * other packages. The caller uses 039 * {@link hirondelle.web4jtools.logview.parser.LogParserInstance} to obtain instances of 040 * this class. 041 */ 042 final class LogParserForJDKDefault implements LogParser { 043 044 /** The assumed date format for logging output - {@value}. You may need to change this to reflect your host's settings. */ 045 public static final String DATE_FORMAT = "d-MMM-yyyy h:mm:ss a"; 046 047 public List<LoggerRecord> parse(String aLogFileContents, ParsedCriteria aCriteria) { 048 List<LoggerRecord> result = new ArrayList<LoggerRecord>(); 049 LoggerRecord currentLoggerRecord = null; 050 StringBuilder currentMsg = new StringBuilder(); //starts with 2nd line content; may have more 051 Scanner scanner = new Scanner(aLogFileContents); 052 while (scanner.hasNextLine()) { 053 String line = scanner.nextLine() + NEW_LINE; 054 if ( isFirstLine(line) ) { 055 fLogger.fine("FIRST: "+ Util.quote(line)); 056 finishOldLoggerRecord(aCriteria, result, currentLoggerRecord, currentMsg); 057 currentLoggerRecord = startNewLoggerRecord(line); 058 } 059 else if ( isSecondLine(line) ) { 060 fLogger.fine("SECOND: "+ Util.quote(line)); 061 currentLoggerRecord.addLevel( extractLevelFromSecond(line) ); 062 currentMsg = new StringBuilder( extractMessageFromSecond(line) ); 063 } 064 else { 065 fLogger.fine("OTHER: "+ Util.quote(line)); 066 currentMsg.append(line); 067 } 068 } 069 finishOldLoggerRecord(aCriteria, result, currentLoggerRecord, currentMsg); 070 scanner.close(); 071 return result; 072 } 073 074 // PRIVATE 075 private static final Logger fLogger = Util.getLogger(LogParserForJDKDefault.class); 076 077 private boolean isFirstLine(String aLine){ 078 return startsWithDateTime(aLine); 079 } 080 081 private boolean startsWithDateTime(String aLine){ 082 //not fixed width. Java date parsing in weird. Accepts trailing chars. 083 //16-Sep-2011 10:32:25 PM hirondelle.web4j... 084 //6-Sep-2011 7:30:53 PM org.apache.cata.... 085 boolean result = false; 086 if(aLine.length()>22){ 087 String date = aLine.substring(0,23).trim(); 088 SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); 089 try { 090 Date testDate = dateFormat.parse(date); 091 result = true; 092 } 093 catch(java.text.ParseException ex) { 094 //do nothing at all 095 } 096 } 097 return result; 098 } 099 100 private boolean isSecondLine(String aLine){ 101 return 102 aLine.startsWith("SEVERE") || 103 aLine.startsWith("WARNING") || 104 aLine.startsWith("INFO") || 105 aLine.startsWith("CONFIG") || 106 aLine.startsWith("FINE") || 107 aLine.startsWith("FINER") || 108 aLine.startsWith("FINEST") 109 ; 110 } 111 112 private LoggerRecord startNewLoggerRecord(String aLine){ 113 //6-Sep-2011 10:55:09 AM hirondelle.web4j.Controller init 114 int idxMethod = aLine.lastIndexOf(SPACE); 115 if(idxMethod == NOT_FOUND) { 116 fLogger.severe("Cannot find Method in " + Util.quote(aLine)); 117 } 118 String method = aLine.substring(idxMethod+1); 119 120 int idxLogger = aLine.lastIndexOf(SPACE, idxMethod-1); 121 if(idxLogger == NOT_FOUND) { 122 fLogger.severe("Cannot find Logger in " + Util.quote(aLine)); 123 } 124 String logger = aLine.substring(idxLogger + 1, idxMethod); 125 126 Date date = parseDate(aLine.substring(0,idxLogger)); 127 return new LoggerRecord(date, logger, method); 128 } 129 130 private Date parseDate(String aDate) { 131 //Since the source date is generated by code, it is less dangerous to use SimpleDateFormat. 132 //6-Sep-2011 10:55:09 AM 133 Date result = null; 134 SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); 135 try { 136 result = dateFormat.parse(aDate); 137 } 138 catch(java.text.ParseException ex) { 139 fLogger.severe("Cannot parse into a date : " + aDate + " using format " + Util.quote(DATE_FORMAT)); 140 } 141 return result; 142 } 143 144 private String extractLevelFromSecond(String aLine){ 145 int idxColon = aLine.indexOf(":"); 146 return aLine.substring(0,idxColon); 147 } 148 149 private String extractMessageFromSecond(String aLine) { 150 int idxColon = aLine.indexOf(":"); 151 return aLine.substring(idxColon+1); 152 } 153 154 private void finishOldLoggerRecord(ParsedCriteria aCriteria, List<LoggerRecord> aResult, LoggerRecord aCurrentLoggerRecord, StringBuilder aCurrentMsg) { 155 if( aCurrentLoggerRecord != null ) { 156 aCurrentLoggerRecord.addMessage(aCurrentMsg.toString()); 157 if( aCriteria.passes(aCurrentLoggerRecord)) { 158 aResult.add(aCurrentLoggerRecord); 159 } 160 } 161 } 162 }