/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.core.pluginapi.event.log;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.Nullable;
import org.rhq.core.domain.event.Event;
import org.rhq.core.domain.event.EventSeverity;
/**
* A {@link LogEntryProcessor} for multi-line log files - provides several abstract methods that subclasses must
* implement.
*
* @author Ian Springer
*/
public abstract class MultiLineLogEntryProcessor implements LogEntryProcessor {
protected final Log log = LogFactory.getLog(this.getClass());
protected String eventType;
protected File logFile;
protected EventSeverity minimumSeverity;
protected Pattern includesPattern;
protected DateFormat dateFormat;
public MultiLineLogEntryProcessor(String eventType, File logFile) {
this.eventType = eventType;
this.logFile = logFile;
}
@Nullable
public Set<Event> processLines(BufferedReader bufferedReader) throws IOException {
// Use a LinkedHashSet so the Events are in the same order as the log entries they correspond to.
Set<Event> events = new LinkedHashSet<Event>();
LogEntry currentEntry = null;
String line;
while ((line = bufferedReader.readLine()) != null) {
currentEntry = processLine(line, events, currentEntry);
}
// We've reached the end of the passed-in buffer, so assume the current entry is complete, and add an Event for
// it.
addEventForCurrentEntry(events, currentEntry);
return events;
}
public void setMinimumSeverity(EventSeverity minimumSeverity) {
this.minimumSeverity = minimumSeverity;
}
public void setIncludesPattern(Pattern includesPattern) {
this.includesPattern = includesPattern;
}
public void setDateFormat(DateFormat dateFormat) {
this.dateFormat = dateFormat;
}
protected LogEntry processLine(String line, Set<Event> events, LogEntry currentEntry) {
Matcher matcher = getPattern().matcher(line);
if (matcher.matches()) {
// A matching line means this is the beginning of a new entry, which tells us the current entry
// (if there is one) has no more additional lines; we can therefore add an Event for that entry.
addEventForCurrentEntry(events, currentEntry);
// And then start building up a new entry...
try {
currentEntry = processPrimaryLine(matcher);
} catch (ParseException e) {
// NOTE: Do not throw an exception here, otherwise none of the remaining lines that were passed in to
// processLines() will get processed.
log.warn("Failed to parse line [" + line + "].");
currentEntry = null;
}
} else {
// If the line didn't match, assume it's an additional line (e.g. part of a stack trace).
if (currentEntry != null) {
currentEntry.appendLineToDetail(line);
}
}
return currentEntry;
}
protected abstract Pattern getPattern();
private void addEventForCurrentEntry(Set<Event> events, LogEntry currentEntry) {
if (currentEntry != null) {
if (currentEntry.getSeverity().isAtLeastAsSevereAs(this.minimumSeverity)
&& (this.includesPattern == null || this.includesPattern.matcher(currentEntry.getDetail()).find())) {
Event event = new Event(this.eventType, this.logFile.getPath(), currentEntry.getDate().getTime(),
currentEntry.getSeverity(), currentEntry.getDetail());
events.add(event);
}
}
}
protected abstract LogEntry processPrimaryLine(Matcher matcher) throws ParseException;
protected abstract DateFormat getDefaultDateFormat();
protected Date parseDateString(String dateString) throws ParseException {
Date timestamp = null;
DateFormat dateFormat = (this.dateFormat != null) ? this.dateFormat : getDefaultDateFormat();
if (dateFormat != null) {
try {
timestamp = dateFormat.parse(dateString);
} catch (java.text.ParseException e) {
throw new ParseException("Unable to parse date [" + dateString + "] using date format [" + dateFormat
+ "].", e);
} catch (RuntimeException e) {
throw new ParseException("Unable to parse date [" + dateString + "] using date format [" + dateFormat
+ "].", e);
}
setDateIfNotSet(timestamp);
}
return timestamp;
}
protected static void setDateIfNotSet(Date timestamp) {
Calendar date = convertToCalendar(timestamp);
// If the format specified a time, but no date, the date will be Jan 1, 1970. In this case, set the date to
// today's date.
if (date.get(Calendar.YEAR) == 1970) {
Calendar currentDate = Calendar.getInstance();
date.set(currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE));
timestamp.setTime(date.getTimeInMillis());
}
}
private static Calendar convertToCalendar(Date timestamp) {
Calendar date = Calendar.getInstance();
date.setTime(timestamp);
return date;
}
protected static class LogEntry {
private Date date;
private EventSeverity severity;
private StringBuilder detail;
public LogEntry(Date date, EventSeverity severity, String detail) {
this.date = date;
this.severity = severity;
this.detail = new StringBuilder(detail);
}
Date getDate() {
return date;
}
EventSeverity getSeverity() {
return severity;
}
String getDetail() {
return detail.toString();
}
void appendLineToDetail(String string) {
this.detail.append("\n");
this.detail.append(string);
}
}
protected static class ParseException extends Exception {
public ParseException(String message) {
super(message);
}
public ParseException(String message, Throwable cause) {
super(message, cause);
}
}
}