package com.github.triceo.splitlog; import com.github.triceo.splitlog.api.*; import com.github.triceo.splitlog.logging.SplitlogLoggerFactory; import com.github.triceo.splitlog.util.SplitlogTailer; import com.github.triceo.splitlog.util.SplitlogThreadFactory; import org.apache.commons.io.input.Tailer; import org.slf4j.Logger; import java.lang.ref.WeakReference; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; /** * Has a sole responsibility of starting and stopping {@link Tailer} thread when * told so by the {@link DefaultLogWatch}. */ final class LogWatchTailingManager { private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool(new SplitlogThreadFactory("tails")); private static final Logger LOGGER = SplitlogLoggerFactory.getLogger(LogWatchTailingManager.class); private final int bufferSize; private MessageBuilder currentlyProcessedMessage; private final long delayBetweenReads; private final AtomicBoolean isReading = new AtomicBoolean(false); private final AtomicBoolean isTailing = new AtomicBoolean(false); private final AtomicLong numberOfTimesThatTailerWasStarted = new AtomicLong(0); private WeakReference<Message> previousAcceptedMessage; private final boolean reopenBetweenReads, ignoreExistingContent; private final TailSplitter splitter; private SplitlogTailer tailer; private final DefaultLogWatch watch; public LogWatchTailingManager(final DefaultLogWatch watch, final LogWatchBuilder builder, final TailSplitter splitter) { this.watch = watch; this.splitter = splitter; this.delayBetweenReads = builder.getDelayBetweenReads(); this.bufferSize = builder.getReadingBufferSize(); this.reopenBetweenReads = builder.isClosingBetweenReads(); this.ignoreExistingContent = !builder.isReadingFromBeginning(); } public DefaultLogWatch getWatch() { return this.watch; } protected void readingFinished() { if (!this.isReading.compareAndSet(true, false)) { return; } LogWatchTailingManager.LOGGER.info("Tailing stopped submitting lines."); if (this.currentlyProcessedMessage != null) { /* * there will be no more lines from the current reading burst; the * currently processed message must be marked as INCOMING with the * possibility of being finished in the subsequent reading burst(s). */ this.getWatch().messageIncoming(this.currentlyProcessedMessage.buildIntermediate(this.splitter)); } } protected void readingStarted() { if (!this.isReading.compareAndSet(false, true)) { return; } LogWatchTailingManager.LOGGER.info("Tailing will now start submitting lines."); } protected void readLine(final String line) { if (!this.isReading.get()) { LogWatchTailingManager.LOGGER.warn("Line '{}' received when the tailer shouldn't have been sending: {}.", line, this); return; } final boolean isMessageBeingProcessed = this.currentlyProcessedMessage != null; if (this.splitter.isStartingLine(line)) { // new message begins if (isMessageBeingProcessed) { // finish old message LogWatchTailingManager.LOGGER.debug("Existing message will be finished."); final Message completeMessage = this.currentlyProcessedMessage.buildFinal(this.splitter); final MessageDeliveryStatus accepted = this.getWatch().messageArrived(completeMessage); this.currentlyProcessedMessage = null; if (accepted == null) { LogWatchTailingManager.LOGGER.info("Message {} rejected at the gate to {}.", completeMessage, this); } else if (accepted == MessageDeliveryStatus.ACCEPTED) { this.previousAcceptedMessage = new WeakReference<>(completeMessage); } else { LogWatchTailingManager.LOGGER .info("Message {} rejected from storage in {}.", completeMessage, this); } } // prepare for new message LogWatchTailingManager.LOGGER.debug("New message is being prepared."); this.currentlyProcessedMessage = new MessageBuilder(line); if (this.previousAcceptedMessage != null) { this.currentlyProcessedMessage.setPreviousMessage(this.previousAcceptedMessage.get()); } } else { // continue present message if (!isMessageBeingProcessed) { LogWatchTailingManager.LOGGER.debug("Disregarding line as trash."); // most likely just a garbage immediately after start return; } LogWatchTailingManager.LOGGER.debug("Existing message is being updated."); this.currentlyProcessedMessage.add(line); } LogWatchTailingManager.LOGGER.debug("Line processing over."); } /** * Start the tailer on a separate thread. Only when a tailer is running can * {@link Follower}s be notified of new {@link Message}s from the log. * * @return True if the start was scheduled, false if scheduled already. */ public boolean start() { if (!this.isTailing.compareAndSet(false, true)) { return false; } final boolean willReadFromEnd = this.willReadFromEnd(); LogWatchTailingManager.LOGGER.debug("Tailer {} ignore existing file contents.", willReadFromEnd ? "will" : "won't"); this.tailer = new SplitlogTailer(this.watch.getWatchedFile(), new LogWatchTailerListener(this), this.delayBetweenReads, this.willReadFromEnd(), this.reopenBetweenReads, this.bufferSize); LogWatchTailingManager.EXECUTOR.submit(this.tailer); final long start = System.nanoTime(); this.tailer.waitUntilStarted(); final long duration = System.nanoTime() - start; LogWatchTailingManager.LOGGER.debug("It took {} ms for the tailing to actually start.", TimeUnit.NANOSECONDS.toMillis(duration)); final long iterationNum = this.numberOfTimesThatTailerWasStarted.incrementAndGet(); LogWatchTailingManager.LOGGER.info("Tailing #{} started for {}.", iterationNum, this.watch); return true; } /** * Stop the tailer thread, preventing any {@link Follower}s from receiving * {@link Message}s. * * @return True if stopped, false if never running. */ public boolean stop() { if (!this.isTailing.get()) { LogWatchTailingManager.LOGGER.debug("Tailer not running, therefore not terminating."); return false; } /* * terminate tailer; we stop the scheduler and the task will therefore * never be started again */ this.tailer.stop(); LogWatchTailingManager.LOGGER.info("Terminated tailing #{} for {}.", this.numberOfTimesThatTailerWasStarted.get(), this.watch); return true; } protected void tailingFinished() { this.isReading.set(false); LogWatchTailingManager.LOGGER.info("Tailing terminated."); if (this.currentlyProcessedMessage != null) { /* * there will be no more lines. the last message must be accepted or * rejected as well. */ this.getWatch().messageArrived(this.currentlyProcessedMessage.buildFinal(this.splitter)); this.currentlyProcessedMessage = null; this.previousAcceptedMessage = null; } } private boolean willReadFromEnd() { if (this.numberOfTimesThatTailerWasStarted.get() > 0) { return true; } else { return this.ignoreExistingContent; } } }