/* * 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; } }