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    }