/* * 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.Closeable; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Queue; import org.cleverbus.common.log.Log; import org.apache.commons.io.FileUtils; import org.apache.commons.io.LineIterator; /** * Iterates over provided log files, parsing and returning {@link LogEvent}s on demand. */ class LogEventParsingIterator implements Iterator<LogEvent>, Closeable { private final LogParser parser; private final LogParserConfig config; private final Queue<File> logFiles; private LineIterator lineIterator; private LogEvent parsedEvent; // event to be returned by this iterator on next() private LogEvent preParsedEvent; // event that was preParsed from a single line, but might have more lines private List<String> groupKey = null; private int totalCount = 0; // events found total private int fileEventsFound = -1; // events per file, resets to 0 on new file private int groupCount = 0; // events in current group, resets to 0 on new group LogEventParsingIterator(LogParser parser, LogParserConfig config, Collection<File> logFiles) throws IOException { this.parser = parser; this.config = config; this.logFiles = new LinkedList<File>(logFiles); nextEvent(); } /** * Ensures that either {@link #parsedEvent} is not null, or the end is reached. * * @throws IOException if there's a problem opening a new file, while advancing to the next event */ private void nextEvent() throws IOException { if (totalCount >= config.getLimit()) { Log.debug("Reached {} events limit - stopping", config.getLimit()); close(); return; } if (parsedEvent != null) { return; } boolean haveMore = !reachedEnd(); while (parsedEvent == null && haveMore) { haveMore = seekToNextEvent(); if (preParsedEvent != null && config.getGroupBy() != null && config.getGroupLimit() != null) { // grouping enabled - check group: List<String> nextGroupKey = getGroupKey(preParsedEvent, config); if (!nextGroupKey.equals(groupKey)) { groupKey = nextGroupKey; // this event starts a new group groupCount = 1; // reset group counter } else if (groupCount >= config.getGroupLimit()) { preParsedEvent = null; // discard the pre-parsed event, as its group is full } else { groupCount++; } } } if (parsedEvent != null) { fileEventsFound++; totalCount++; } } /** * Advance to the position where next line is available, * regardless of whether it passes filter requirements or not. * On the way to this position any lines that are not log events * will be appended to the previous log event. * <p/> * If this call is successful, either {@link #parsedEvent} will be a new event, * or {@link #preParsedEvent} will not be null; or both. * * @return true, if there might be more events; false otherwise * @throws IOException if there's a problem opening a new file */ private boolean seekToNextEvent() throws IOException { LogEvent nextEvent = config.createLogEvent(); LineIterator iterator; while ((iterator = getLineIterator()) != null) { while (iterator.hasNext()) { // process the line: LogEvent event = parser.parseLine(iterator.next(), nextEvent, preParsedEvent, config); // check what to do next: if (preParsedEvent != null && event != preParsedEvent) { // there is pre-parsed event, but the line was NOT appended to it // => pre-parsed event will not get any more lines, it can be considered fully parsed parsedEvent = preParsedEvent; // graduate pre-parsed event - it'll be the next event preParsedEvent = event; // next event that was found (if any) is now pre-parsed return true; // found full pre-parsed event => success } else if (event == nextEvent) { // nextEvent is now pre-parsed, but previous pre-parsed event is null, so no event graduated preParsedEvent = nextEvent; return true; } // otherwise line was ignored or added to the pre-parsed event, nothing really changed } } return false; // failure } private boolean reachedEnd() throws IOException { return getLineIterator() == null; } /** * Returns a line iterator to process next/current file, opening the next file, if necessary. * * @return line iterator for the current/next file; or null if no files left */ private LineIterator getLineIterator() throws IOException { if (lineIterator != null && lineIterator.hasNext()) { return lineIterator; } // discard current line iterator LineIterator.closeQuietly(lineIterator); lineIterator = null; if (fileEventsFound == 0) { Log.debug("No events in the last file, closing prematurely"); close(); // optimize: last file had no events, no point continuing } else if (!logFiles.isEmpty()) { File file = logFiles.poll(); Log.debug("Opening {}", file); lineIterator = FileUtils.lineIterator(file); fileEventsFound = 0; // restart per-file counter } return lineIterator; } private List<String> getGroupKey(LogEvent event, LogParserConfig config) { List<String> groupKey = new ArrayList<String>(config.getGroupBy().size()); for (int propertyIndex = 0; propertyIndex < event.getPropertyCount(); propertyIndex++) { if (config.getGroupBy().contains(event.getPropertyNames().get(propertyIndex))) { groupKey.add(String.valueOf(event.getProperties()[propertyIndex])); } } return groupKey; } @Override public boolean hasNext() { return parsedEvent != null; } @Override public LogEvent next() { LogEvent result = parsedEvent; parsedEvent = null; try { nextEvent(); } catch (IOException exc) { throw new IllegalStateException("Error prefetching next event", exc); } return result; } @Override public void remove() { throw new UnsupportedOperationException(); } @Override public void close() { LineIterator.closeQuietly(lineIterator); lineIterator = null; logFiles.clear(); Log.debug("Closed"); } }