All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gemstone.gemfire.internal.LogFileParser Maven / Gradle / Ivy

There is a newer version: 2.0-BETA
Show newest version
/*
 * Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License. You
 * may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License. See accompanying
 * LICENSE file.
 */
package com.gemstone.gemfire.internal;

import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
import java.io.*;
import java.text.*;
import java.util.*;

/**
 * Parses a log file written by a {@link
 * com.gemstone.gemfire.i18n.LogWriterI18n} into {@link
 * LogFileParser.LogEntry}s.  It behaves sort of like an {@link
 * java.util.StringTokenizer}. 
 *
 * @author David Whitlock
 * @author Bruce Schuchardt
 *
 * @since 3.0
 */
public class LogFileParser  {
  private static final boolean TRIM_TIMESTAMPS = Boolean.getBoolean("mergelogs.TRIM_TIMESTAMPS");
  
  private static final boolean NEWLINE_AFTER_HEADER = Boolean.getBoolean("mergelogs.NEWLINE_AFTER_HEADER");

  private static final boolean TRIM_NAMES = Boolean.getBoolean("mergelogs.TRIM_NAMES");
  
  /** Text that signifies the start of a JRockit-style thread dump */
  private static final String FULL_THREAD_DUMP =
    "===== FULL THREAD DUMP ===============";

  ///////////////////////  Instance Fields  ///////////////////////

  /** The name of the log file being parsed */
  private final String logFileName;

  /** The name of the log file plus a colon and space */
  private final String extLogFileName;
  
  /** The buffer to read the log file from */
  private BufferedReader br;

  /** Are there more entries to parser? */
  private boolean hasMoreEntries;

  /** The pattern used to match the first line of a log entry */
//  private Pattern pattern;

  /** The timestamp of the entry being parsed */
  private String timestamp;

  /** StringBuffer containing the text of the entry we're parsing */
  private StringBuffer sb;
  
  /** whether we're still reading the first line of the first entry */
  private boolean firstEntry = true;
  
  /**
   * StringBuffer containing white space that is the same length as
   * logFileName plus ": ", in a monospace font when tabs are 8 chars long
   */
  private final StringBuffer whiteFileName;
  
  /** whether to suppress blank lines in output */
  private boolean suppressBlanks;

  //////////////////////  Constructors  //////////////////////

  /**
   * Creates a new LogFileParser that reads a log from a
   * given BufferedReader.  Blanks are not suppressed, and
   * non-timestamped lines are emitted as-is.
   *
   * @param logFileName
   *        The name of the log file being parsed.  This is appended
   *        to the entry.  If logFileName is
   *        null nothing will be appended.
   * @param br
   *        Where to read the log from
   */
  public LogFileParser(String logFileName, BufferedReader br) {
    this(logFileName, br, false, false);
  }
  
  /**
   * Creates a new LogFileParser that reads a log from a
   * given BufferedReader.
   *
   * @param logFileName
   *        The name of the log file being parsed.  This is appended
   *        to the entry.  If logFileName is
   *        null nothing will be appended.
   * @param br
   *        Where to read the log from
   * @param tabOut
   *        Whether to add white-space to non-timestamped lines to align them
   *        with lines containing file names.
   * @param suppressBlanks
   *        whether to suppress blank lines
   */
  public LogFileParser(String logFileName, BufferedReader br,
      boolean tabOut, boolean suppressBlanks) {
    this.logFileName = logFileName;
    this.br = br;
    this.hasMoreEntries = true;
//    this.pattern = 
//      Pattern.compile("\\[\\w+ (\\d\\d\\d\\d/\\d\\d/\\d\\d \\d\\d:\\d\\d:\\d\\d\\.\\d\\d\\d) .*");
    this.timestamp = null;
    this.sb = new StringBuffer();
    this.suppressBlanks = suppressBlanks;
    this.whiteFileName = new StringBuffer();
    if (tabOut) {
      int numTabs = (logFileName.length()+2) / 8;
      for (int i=0; i0; i--) {
        whiteFileName.append(' ');
      }
    }
    if (this.logFileName != null) {
      this.extLogFileName = this.logFileName + ": ";
    }
    else {
      this.extLogFileName = null;
    }
  }

  ////////////////////  Instance Methods  ////////////////////

  /**
   * Returns whether or not there are any more entries in the file to
   * be parser.
   */
  public boolean hasMoreEntries() {
    return this.hasMoreEntries;
  }

  /** copy the timestamp out of a log entry, if there is one, and return it.
   *  if there isn't a timestamp, return null
   */
  private String getTimestamp(String line) {
    int llen = line.length();
    String result = null;
    if (llen > 10) {
      if (line.charAt(0) == '[') {
        if ((line.charAt(1) == 'i' &&
             line.charAt(2) == 'n' &&
             line.charAt(3) == 'f' /*&&
             line.charAt(4) == 'o'*/)   ||
            (line.charAt(1) == 'f' &&
             line.charAt(2) == 'i' &&
             line.charAt(3) == 'n' /*&&
             line.charAt(4) == 'e'*/)   ||
            (line.charAt(1) == 'w' &&
             line.charAt(2) == 'a' &&
             line.charAt(3) == 'r' /*&&
             line.charAt(4) == 'n' &&
             line.charAt(5) == 'i' &&
             line.charAt(6) == 'n' &&
             line.charAt(7) == 'g'*/)   ||
            (line.charAt(1) == 's' &&
             line.charAt(2) == 'e' &&
             line.charAt(3) == 'v' /*&&
             line.charAt(4) == 'e' &&
             line.charAt(5) == 'r' &&
             line.charAt(6) == 'e'*/)   ||
            (line.charAt(1) == 'c' &&
             line.charAt(2) == 'o' &&
             line.charAt(3) == 'n' /*&&
             line.charAt(4) == 'f' &&
             line.charAt(5) == 'i' &&
             line.charAt(6) == 'g'*/)   ||
            (line.charAt(1) == 'e' &&
             line.charAt(2) == 'r' &&
             line.charAt(3) == 'r' /*&&
             line.charAt(4) == 'o' &&
             line.charAt(5) == 'r'*/)   ||
             (line.charAt(1) == 's' &&
              line.charAt(2) == 'e' &&
              line.charAt(3) == 'c' &&
              line.charAt(4) == 'u' &&
              line.charAt(5) == 'r')) {
          int sidx = 4;
          while (sidx < llen && line.charAt(sidx) != ' ') {
            sidx++;
          }
          int endIdx = sidx + 24;
          if (endIdx < llen) {
            result = line.substring(sidx+1, endIdx+1);
          }
        }
      }
    }
    return result;
  }
  
  /**
   * Returns the next entry in the log file.  The last entry will be
   * an instance of {@link LogFileParser.LastLogEntry}.
   */
  public LogEntry getNextEntry() throws IOException {
    LogEntry entry = null;

    while (br.ready()) {
      String lineStr = br.readLine();
      if (lineStr == null) {
        break;
      }
      int llen = lineStr.length();
      int lend = llen;
      if (this.suppressBlanks || this.firstEntry) {
        // trim the end of the line
        while (lend > 1  &&  Character.isWhitespace(lineStr.charAt(lend-1))) {
          lend--;
        }
        if (lend == 0) {
          //System.out.println(this.logFileName + ": skipping line '" + lineStr + "'");
          continue;
        }
      }
      
      StringBuffer line = new StringBuffer(lineStr);
      if (lend != llen) {
        line.setLength(lend);
        llen = lend;
      }

//      Matcher matcher = pattern.matcher(line);
      String nextTimestamp = getTimestamp(lineStr);
          
      // See if we've found the beginning of a new log entry.  If so, bundle
      // up the current string buffer and return it in a LogEntry representing
      // the currently parsed text
      if (nextTimestamp != null) {

        if (timestamp != null && TRIM_TIMESTAMPS) {
          int tsl = timestamp.length();
          if (tsl > 0) {
            // find where the year/mo/dy starts and delete it and the time zone
            int start = 5;
            if (line.charAt(start) != ' ')         // info & fine
              if (line.charAt(++start) != ' ')     // finer & error
                if (line.charAt(++start) != ' ')   // finest, severe, config
                  if (line.charAt(++start) != ' ') // warning
                    start = 0;
            if (start > 0) {
              line.delete(start+25, start+29);  // time zone
              line.delete(start, start+11);     // date
              if (TRIM_NAMES) {
                int idx2 = line.indexOf("<", + 12);
                if (idx2 > start+13) {
                  line.delete(start+13, idx2-1);
                }
              }
            }
          }
          if (NEWLINE_AFTER_HEADER) {
            int idx = line.indexOf("tid=");
            if (idx > 0) {
              idx = line.indexOf("]", idx+4);
              if (idx+1 < line.length()) {
                line.insert(idx+1, "\n ");
              }
            }
          }
        }

        if (timestamp != null) {
          entry = new LogEntry(timestamp, sb.toString(), this.suppressBlanks);
        }

        timestamp = nextTimestamp;

        if (!this.firstEntry) {
          sb = new StringBuffer(500);
        }
        else {
          this.firstEntry = false;
        }
        if (this.extLogFileName != null) {
          sb.append(this.extLogFileName);
        }

      }
      else if (line.indexOf(FULL_THREAD_DUMP) != -1) {
        // JRockit-style thread dumps have time stamps!
        String dump = lineStr;
        lineStr = br.readLine();
        if (lineStr == null) {
          break;
        }
        DateFormat df = new SimpleDateFormat("E MMM d HH:mm:ss yyyy");
        df.setLenient(true);
        try {
          Date date = df.parse(lineStr);
          
          if (timestamp != null) {
            // We've found the end of a log entry
            entry = new LogEntry(timestamp, sb.toString());
          }

          df = new SimpleDateFormat(LogWriterImpl.FORMAT);
          timestamp = df.format(date);
          lineStr = dump;

          sb = new StringBuffer();
          if (this.extLogFileName != null) {
            sb.append(this.extLogFileName);
          }
          sb.append("[dump ");
          sb.append(timestamp);
          sb.append("]\n\n");

        } catch (ParseException ex) {
          // Oh well...
          sb.append(dump);
        }
      }
      else {
        sb.append(this.whiteFileName);
      }

      sb.append(line);
      sb.append("\n");

      if (entry != null) {
        return entry;
      }
    }

    if (timestamp == null) {
      // The file didn't contain any log entries.  Just use the
      // current time
      DateFormat df = new SimpleDateFormat(LogWriterImpl.FORMAT);
//      Date now = new Date();
      timestamp = df.format(new Date());

      StringWriter sw = new StringWriter();
      PrintWriter pw = new PrintWriter(sw, true);

      LocalLogWriter tempLogger =
        new LocalLogWriter(LogWriterImpl.ALL_LEVEL, pw);
      tempLogger.info(LocalizedStrings.LogFileParser_MISSING_TIME_STAMP);
      pw.flush();
      sb.insert(0, "\n\n");
      sb.insert(0, sw.toString().trim());
      sb.insert(0, this.extLogFileName);
    }

    // Place the final log entry
    entry = new LastLogEntry(timestamp, sb.toString());
    this.sb = null;
    this.hasMoreEntries = false;
    return entry;
  }

  //////////////////////  Main Program  ///////////////////////

  /**
   * Main program that simply parses a log file and prints out the
   * entries.  It is used for testing purposes.
   */
  public static void main(String[] args) throws Throwable {
    if (args.length < 1) {
      System.err.println(LocalizedStrings.LogFileParser_MISSING_LOG_FILE_NAME.toLocalizedString());
      System.exit(1);
    }

    String logFileName = args[0];
    BufferedReader br =
      new BufferedReader(new FileReader(logFileName));
    LogFileParser parser = new LogFileParser(logFileName, br, false, false);
    PrintWriter pw = new PrintWriter(System.out);
    while (parser.hasMoreEntries()) {
      LogEntry entry = parser.getNextEntry();
      entry.writeTo(pw);
    }
  }


  //////////////////////  Inner Classes  //////////////////////

  /**
   * A parsed entry in a log file.  Note that we maintain the entry's
   * timestamp as a String. 
   * {@link java.text.DateFormat#parse(java.lang.String) Parsing} it was too expensive.
   */
  static class LogEntry  {
    /** Timestamp of the log entry */
    private String timestamp;

    /** The contents of the log entry */
    private String contents;
    
    /** whether extraneous blank lines are being suppressed */
    private boolean suppressBlanks;

    ////////////////////  Constructors  ////////////////////

    /**
     * Creates a new log entry with the given timestamp and contents
     */
    public LogEntry(String timestamp, String contents) {
      this.timestamp = timestamp;
      this.contents = contents;
    }

    /**
     * Creates a new log entry with the given timestamp and contents
     */
    public LogEntry(String timestamp, String contents, boolean suppressBlanks) {
      this.timestamp = timestamp;
      this.contents = contents.trim();
      this.suppressBlanks = suppressBlanks;
    }

    ////////////////////  Instance Methods  ////////////////////

    /**
     * Returns the timestamp of this log entry
     */
    public String getTimestamp() {
      return this.timestamp;
    }

    /**
     * Returns the contents of this log entry
     *
     * @see #writeTo
     */
    String getContents() {
      return this.contents;
    }
    
    /**
     * Writes the contents of this log entry to a
     * PrintWriter. 
     */
    public void writeTo(PrintWriter pw) {
      pw.println(this.contents);
      if (!this.suppressBlanks) {
        pw.println("");
      }
      pw.flush();
    }

    /**
     * Is this entry the last log entry?
     */
    public boolean isLast() {
      return false;
    }
  }

  /**
   * The last log entry read from a log file.  We use a separate class
   * to avoid the overhead of an extra boolean field in
   * each {@link LogFileParser.LogEntry}.
   */
  static class LastLogEntry extends LogEntry  {
    public LastLogEntry(String timestamp, String contents) {
      super(timestamp, contents);
    }    

    @Override
    public boolean isLast() {
      return true;
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy