package uk.bl.monitrix.heritrix; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.Iterator; import play.Logger; /** * An incremental log file reader that emulates UNIX 'tail -f'-like read behavior. * @author Rainer Simon <rainer.simon@ait.ac.at> */ public class IncrementalLogfileReader { private static long TEN_MINUTES = 60 * 10000; private File logFile; private BufferedReader reader; private long linesRead = 0; private long lastModifiedValueAtLastRead = 0; private long lastSize = 0; public IncrementalLogfileReader(String filename) throws FileNotFoundException { File log = new File(filename); if (!log.exists()) throw new FileNotFoundException(filename + " not found"); this.logFile = new File(log.getAbsolutePath()); this.lastSize = logFile.length(); this.reader = new BufferedReader(new FileReader(log)); } public String getPath() { return logFile.getAbsolutePath(); } /** * * @return * @throws IOException */ public boolean isRenamed() throws IOException { // If the log has become smaller - RENAMED! if (logFile.length() < lastSize) return true; // This may work better, as the problem is the 'known' log file being renamed: /* if( logFile.getAbsolutePath() != this.getPath() ) return true; */ // If the log was modified, but the reader didn't read anything in the past 10 minutes - RENAMED! // NOTE that this does not really work - if there are many log files, the it may take > 10 mins to get around to a log file. /* if (lastModifiedValueAtLastRead < logFile.lastModified() - TEN_MINUTES) return true; */ return false; } public void skipLines( long linesToSkip ) { for (long i=0; i<linesToSkip; i++) { try { reader.readLine(); linesRead++; } catch (IOException e) { Logger.error("Exception '"+e+"' while skipping "+linesToSkip+" lines of log file: "+this.getPath()); } } } /** * Returns an iterator over all log entries that have not yet been consumed through * this {@link IncrementalLogfileReader} instance (including those that may have been * added to the underlying log file in the mean time). * @return the iterator */ public Iterator<LogFileEntry> newIterator() { try { return new FollowingLogIterator(reader); } catch (IOException e) { // Should never happen as we've already checked that the file exists // in the constructor! throw new RuntimeException(e); } } public long getNumberOfLinesRead() { return linesRead; } private class FollowingLogIterator implements Iterator<LogFileEntry> { private BufferedReader reader; private String nextLine; LogFileEntry next = new LogFileEntry(); FollowingLogIterator(BufferedReader reader) throws IOException { this.reader = reader; nextLine = reader.readLine(); linesRead++; } @Override public boolean hasNext() { return nextLine != null; } @Override public LogFileEntry next() { try { next.init(logFile.getAbsolutePath(), nextLine); nextLine = reader.readLine(); linesRead++; lastModifiedValueAtLastRead = logFile.lastModified(); return next; } catch (IOException e) { // Should never happen as we've already checked that the file exists // in the constructor! throw new RuntimeException(e); } } @Override public void remove() { // Not supported throw new UnsupportedOperationException(); } } }