/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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.
*/
package batterytest.greplogs;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class LogConsumer {
private final List expectedExceptions = new ArrayList();
private boolean skipLogMsgs = false;
private boolean infoMsgFlag = false;
private int eatLines = 0;
private boolean tmpErrFlag = false;
private int tmpErrLines = 0;
private boolean saveFlag = false;
private int savelinenum = 0;
private final List testExpectStrs;
StringBuilder all = null;
private int lineNumber;
private String fileName;
HashMap individalErrorCount = new HashMap();
private final int repeatLimit;
private static final Pattern ExpectedExceptionPattern =
Pattern.compile("<ExpectedException action=(add|remove)>(.*)</ExpectedException>");
private static final Pattern logPattern =
Pattern.compile("^\\[(?:fatal|error|warn|info|debug|trace|severe|warning|fine|finer|finest)");
private static final Pattern blankPattern = Pattern.compile("^\\s*$");
/**
* Any messages at these levels will be skipped
*/
private static final Pattern skipLevelPattern =
Pattern.compile("^\\[(?:warn|warning|info|debug|trace|fine|finer|finest)");
private static final Pattern fatalOrErrorPattern = Pattern.compile("^\\[(?:fatal|error|severe)");
private static final Pattern causedByPattern = Pattern.compile("Caused by");
private static final Pattern shortErrPattern =
Pattern.compile("^\\[[^\\]]+\\](.*)$", Pattern.MULTILINE | Pattern.DOTALL);
private static final Pattern wroteExceptionPattern =
Pattern.compile("\\[debug.*Wrote exception:");
private static final Pattern rmiWarnPattern = Pattern.compile(
"^WARNING: Failed to .*java.rmi.ConnectException: Connection refused to host: .*; nested exception is:");
private static final Pattern javaLangErrorPattern = Pattern.compile("^java\\.lang\\.\\S+Error$");
private static final Pattern exceptionPattern = Pattern.compile("Exception:");
private static final Pattern exceptionPattern2 =
Pattern.compile("( [\\w\\.]+Exception: (([\\S]+ ){0,6}))");
private static final Pattern exceptionPattern3 = Pattern.compile("( [\\w\\.]+Exception)$");
private static final Pattern exceptionPattern4 = Pattern.compile("^([^:]+: (([\\w\"]+ ){0,6}))");
private static final Pattern misformatedI18nMessagePattern = Pattern.compile("[^\\d]\\{\\d+\\}");
private static final Pattern rvvBitSetMessagePattern =
Pattern.compile("RegionVersionVector.+bsv\\d+.+bs=\\{\\d+\\}");
/** Limit long errors to this many lines */
private static int ERROR_BUFFER_LIMIT = 128;
public LogConsumer(boolean skipLogMsgs, List testExpectStrs, String fileName, int repeatLimit) {
super();
this.skipLogMsgs = skipLogMsgs;
this.testExpectStrs = testExpectStrs;
this.fileName = fileName;
this.repeatLimit = repeatLimit;
}
public StringBuilder consume(CharSequence line) {
{
lineNumber++;
Matcher m = ExpectedExceptionPattern.matcher(line);
if (m.find()) {
if (m.group(1).equals("add")) {
expectedExceptions.add(Pattern.compile(m.group(2)));
} else {
// assume add and remove are the only choices
expectedExceptions.remove(Pattern.compile(m.group(2)));
}
return null;
}
}
if (skipLogMsgs) {
if (infoMsgFlag) {
if (logPattern.matcher(line).find()) {
infoMsgFlag = false;
} else if (blankPattern.matcher(line).matches()) {
infoMsgFlag = false;
return null;
} else {
return null;
}
}
if (skipLevelPattern.matcher(line).find()) {
infoMsgFlag = true;
return null;
}
}
if (eatLines != 0) {
eatLines--;
return null;
} else {
if (saveFlag || fatalOrErrorPattern.matcher(line).find()) {
if (!saveFlag) {
saveFlag = true;
tmpErrFlag = true;
if (checkExpectedStrs(line, expectedExceptions)) {
saveFlag = false;
tmpErrFlag = false;
tmpErrLines = 0;
}
if (tmpErrFlag) {
tmpErrLines = 1;
all = new StringBuilder(line);
all.append("\n");
savelinenum = lineNumber;
}
} else {
if (causedByPattern.matcher(line).find()) {
// This code used to stop appending if a causedBy was seen.
// But we want the causedBy stack trace to also be included
// in the suspect StringBuilder.
// The main thing is we do not want to call checkExpectedStrs
// with this "caused by" line.
} else if (checkExpectedStrs(line, expectedExceptions)) {
// reset the counters and throw it all away if it matches
// one of the registered expected strings
tmpErrFlag = false;
tmpErrLines = 0;
saveFlag = false;
}
// We save all the lines up to the next blank line so we're
// looking for a blank line here
if (blankPattern.matcher(line).matches()) {
// we found a blank line so print the suspect string
// and reset the savetag flag
saveFlag = false;
Matcher m = shortErrPattern.matcher(all.toString());
if (m.matches()) {
String shortName = m.group(1);
Integer i = (Integer) individalErrorCount.get(shortName);
Integer occurances = new Integer((i == null) ? 1 : i.intValue() + 1);
individalErrorCount.put(shortName, occurances);
return enforceErrorLimit(occurances.intValue(), all.toString(),
// reader.getLineNumber(),
savelinenum, fileName);
} else {
// error in determining shortName, wing it
return enforceErrorLimit(1, all.toString(), lineNumber, fileName);
}
}
// we're still saving lines to append them on to all which contains
// all the lines we're trying to save
if (tmpErrFlag) {
if (tmpErrLines < ERROR_BUFFER_LIMIT) {
tmpErrLines++;
all.append(line).append("\n");
}
if (tmpErrLines == ERROR_BUFFER_LIMIT) {
tmpErrLines++; // increment to prevent this line from repeating
all.append("GrepLogs: ERROR_BUFFER_LIMIT limit reached,")
.append(" the error was too long to display completely.\n");
}
}
}
// unique condition for when bridge server see log exception and
// logging level is set to fine. Message looks like this:
// [fine 2005/10/25 17:53:13.586 PDT gemfire2 Server connection from
// hobbes.gemstone.com:34466-0xf4 nid=0x23e40f1] Server connection from
// hobbes.gemstone.com:34466: Wrote exception:
// org.apache.geode.cache.EntryNotFoundException: remote-destroy-key
// also now handles a JMX WARNING
} else if (wroteExceptionPattern.matcher(line).find()
|| rmiWarnPattern.matcher(line).find()) {
// Eat only the single EntryNotFound Exception
eatLines = 1;
// if we are here then the line didn't have severe or error in it and
// didn't meet any special cases that require eating lines
// Check for other kinds of exceptions. This is by no means inclusive
// of all types of exceptions that could occur and some ARE missed.
} else if (exceptionPattern.matcher(line).find() || javaLangErrorPattern.matcher(line).find()
|| (misformatedI18nMessagePattern.matcher(line).find()
&& !(skipLevelPattern.matcher(line).find()
&& rvvBitSetMessagePattern.matcher(line).find()))) {
if (!checkExpectedStrs(line, expectedExceptions)) {
// it's the Exception colon that we want to find
// along with the next six words and define to shortline
// shortline is only used for the unique sting to count the
// number of times an exception match occurs. This is so
// we can suppress further printing if we hit the limit
Matcher m2 = exceptionPattern2.matcher(line);
Matcher m3 = exceptionPattern3.matcher(line);
Matcher m4 = exceptionPattern4.matcher(line);
String shortName = null;
if (m2.find()) {
shortName = m2.group(1);
} else if (m3.find()) {
shortName = m3.group(1);
} else if (m4.find()) {
shortName = m4.group(1);
}
if (shortName != null) {
Integer i = (Integer) individalErrorCount.get(shortName);
Integer occurances = new Integer((i == null) ? 1 : i.intValue() + 1);
individalErrorCount.put(shortName, occurances);
return enforceErrorLimit(occurances.intValue(), line + "\n", lineNumber, fileName);
} else {
return enforceErrorLimit(1, line + "\n", lineNumber, fileName);
}
}
}
}
return null;
}
public StringBuilder close() {
if (saveFlag) {
// Bug fix for severe that occurs at the end of a log file. Since we
// collect lines up to a blank line that never happens this prints the
// collection of in process suspect strings if we close the file and
// we're still trying to save lines
saveFlag = false;
return enforceErrorLimit(1, all.toString(), savelinenum, fileName);
}
return null;
}
private boolean checkExpectedStrs(CharSequence line, List expectedExceptions) {
for (int i = 0; i < expectedExceptions.size(); i++) {
Pattern p = (Pattern) expectedExceptions.get(i);
if (p.matcher(line).find())
return true;
}
for (int i = 0; i < testExpectStrs.size(); i++) {
Pattern p = (Pattern) testExpectStrs.get(i);
if (p.matcher(line).find())
return true;
}
return false;
}
private StringBuilder enforceErrorLimit(int hits, String line, int linenum, String filename) {
if (hits <= repeatLimit) {
StringBuilder buffer = new StringBuilder();
buffer.append("-----------------------------------------------------------------------\n")
.append("Found suspect string in ").append(filename).append(" at line ").append(linenum)
.append("\n\n").append(line).append("\n");
return buffer;
}
if (hits == repeatLimit) {
StringBuilder buffer = new StringBuilder();
buffer.append("\n\nHit occurrence limit of ").append(hits).append(" for this string.\n")
.append("Further reporting of this type of error will be suppressed.\n");
return buffer;
}
return null;
}
}