com.gemstone.gemfire.internal.LogFileParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gemfire-core Show documentation
Show all versions of gemfire-core Show documentation
SnappyData store based off Pivotal GemFireXD
/*
* 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;
}
}
}