/*
* Copyright (C) 2015
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.cleverbus.admin.services.log;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.regex.Matcher;
import org.cleverbus.common.log.Log;
import org.apache.commons.io.comparator.LastModifiedFileComparator;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* Parses log files based on date and returns lines which follow specific patterns.
*/
@Component
public class LogParser {
// log file format: logFile_%d{yyyy-MM-dd}_%i.log
public static final DateTimeFormatter FILE_DATE_FORMAT = DateTimeFormat.forPattern("yyyy-MM-dd");
public static final String FILE_EXTENSION = "log";
/**
* Absolute path to the folder with log files.
*/
@Value("${log.folder.path}")
private String logFolderPath;
public File[] getLogFiles(final DateTime date) throws FileNotFoundException {
File logFolder = new File(logFolderPath);
if (!logFolder.exists() || !logFolder.canRead()) {
throw new FileNotFoundException("there is no readable log folder - " + logFolderPath);
}
final String logDateFormatted = FILE_DATE_FORMAT.print(date);
final long dateMillis = date.getMillis();
File[] files = logFolder.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
String name = file.getName();
return name.endsWith(FILE_EXTENSION) // it's a log file
&& name.contains(logDateFormatted) // it contains the date in the name
&& file.lastModified() >= dateMillis; // and it's not older than the search date
}
});
Arrays.sort(files, LastModifiedFileComparator.LASTMODIFIED_COMPARATOR);
if (files.length == 0) {
Log.debug("No log files ending with {}, containing {}, modified after {}, at {}",
FILE_EXTENSION, logDateFormatted, date, logFolderPath);
} else {
Log.debug("Found log files for {}: {}", date, files);
}
return files;
}
public Iterator<LogEvent> getLogEventIterator(LogParserConfig config, Collection<File> files) throws IOException {
return new LogEventParsingIterator(this, config, files);
}
/**
* Processes the next log line by either appending it to the last log event,
* if it's not a valid log line and last log event wasn't ignored (appendTo != null);
* or by parsing it into a new event (parseTo).
*
* @param line the log line to process
* @param parseTo the next log event to parse line into
* @param appendTo the log event to append non-event lines to
* @param config log parser config
* @return appendTo event, if the line was appended to it; parseTo event if the line was fully parsed into this event;
* or null if the line was ignored
*/
LogEvent parseLine(String line, LogEvent parseTo, LogEvent appendTo, LogParserConfig config) {
Matcher dateMatcher = config.getDatePattern().matcher(line);
if (!dateMatcher.lookingAt()) {
return parseFailed(line, appendTo);
}
DateTime eventDate = getDate(dateMatcher.group(1), config);
if (eventDate == null) {
return parseFailed(line, appendTo);
}
// line might still not match properties and therefore not be a new log event,
// so don't stop just yet, even if the date is wrong
boolean skipLogLine = eventDate.isBefore(config.getFromDate());
if (skipLogLine && appendTo == null) {
return null; // no point continuing, since this line wouldn't be appended anyway
}
parseTo.setDate(eventDate);
String unmatched = line.substring(0, dateMatcher.start())
+ line.substring(dateMatcher.end(), line.length());
// date matches, but is the line a new log event line?
Matcher propertiesMatcher = config.getPropertiesPattern().matcher(unmatched);
if (!propertiesMatcher.lookingAt()) {
return parseFailed(line, appendTo);
}
if (skipLogLine || !parseEventProperties(propertiesMatcher, parseTo, config)) {
return null;
}
if (unmatched != null && config.getMsg() != null && !unmatched.contains(config.getMsg())) {
return null;
}
unmatched = unmatched.substring(0, propertiesMatcher.start())
+ unmatched.substring(propertiesMatcher.end(), unmatched.length());
parseTo.setMessage(unmatched);
return parseTo;
}
private LogEvent parseFailed(String line, LogEvent appendTo) {
if (appendTo != null) {
appendTo.appendMessage("\n" + line);
}
return appendTo;
}
/**
* Parses a log line into a new LogEvent,
* verifying fields against required values specified by {@link LogParserConfig#getFilter()}.
*
* @param matcher the matcher generated based on {@link LogParserConfig#getPropertiesPattern()}
* @return true if log event properties were parsed from the matcher; false otherwise
*/
private boolean parseEventProperties(Matcher matcher, LogEvent logEvent, LogParserConfig config) {
assert matcher.groupCount() >= config.getPropertyCount();
for (int propertyIndex = 0; propertyIndex < logEvent.getPropertyCount(); propertyIndex++) {
String propertyValue = matcher.group(propertyIndex + 1);
if (!config.isMatchesFilter(propertyValue, propertyIndex)) {
return false;
}
logEvent.getProperties()[propertyIndex] = propertyValue;
}
return true;
}
private DateTime getDate(String dateString, LogParserConfig config) {
try {
return config.getDateFormat().parseDateTime(dateString);
} catch (IllegalArgumentException exc) {
// the date is not in the correct format - ignore
return null;
}
}
public String getLogFolderPath() {
return logFolderPath;
}
public void setLogFolderPath(String logFolderPath) {
this.logFolderPath = logFolderPath;
}
}