/*******************************************************************************
* Copyright (c) 2011 GigaSpaces Technologies Ltd. All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*******************************************************************************/
package org.cloudifysource.usm.tail;
import java.io.File;
import java.io.FilenameFilter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
/**
* tail a RollingFileAppender logs folder without interfering with the RFA rolling action. in-order to avoid locking the
* file and by that preventing the RFA from rolling the file, this tailer will sample all files periodically without
* opening the files and when finding that a file has been modified, only then open the file, "grab" the newly added
* lines and close the file when done.
*
* @author adaml
*
*/
public class RollingFileAppenderTailer implements Runnable {
/*********
* Handler interface which allows a client to delegate the handling of new lines found by the tailer to a custom
* class.
*
* @author barakme
*
*/
public interface LineHandler {
/****************
* Called when a new line is found.
*
* @param fileName
* .
* @param line
* .
*/
void handleLine(final String fileName, final String line);
}
/******
* Logger implementation of the line handler interface.
* @author barakme
*
*/
private static class DefaultLineHandler implements LineHandler {
@Override
public void handleLine(final String fileName, final String line) {
logger.info(line);
}
}
private static final String BREAK_BY_LINES_NO_EMPTY_LINES_REGEX = "\\r?\\n+";
private static final int DEFAULT_SAMPLING_DELAY = 2000;
private final String logsDirectory;
private final String regex;
private final Pattern lineSplitPattern = Pattern.compile(BREAK_BY_LINES_NO_EMPTY_LINES_REGEX);
private final Map<String, RollingFileReader> logFileMap = new HashMap<String, RollingFileReader>();
private static java.util.logging.Logger logger = java.util.logging.Logger
.getLogger(RollingFileAppenderTailer.class.getName());
private LineHandler handler = new DefaultLineHandler();
/**
* Create a new RollingFileAppenderTailer given the file-name regex and the directory where the log files will be
* located.
*
* @param dir
* - the path to the directory of the log files to be tailed.
* @param regex
* - regular expression for file names to be tailed.
*/
public RollingFileAppenderTailer(final String dir, final String regex) {
this.logsDirectory = dir;
this.regex = regex;
}
/****************
* Creates a new Tailer with a callback that handles each new line.
*
* @param dir
* - the path to the directory of the log files to be tailed.
* @param regex
* - regular expression for file names to be tailed.
* @param handler
* - the callback that handles new lines.
*/
public RollingFileAppenderTailer(final String dir, final String regex, final LineHandler handler) {
this.logsDirectory = dir;
this.regex = regex;
this.handler = handler;
}
/**
* Create a new RollingFileAppenderTailer given the file-name regex, the directory where the log files will be saved
* and the time period between sampling the files.
*
* @param dir
* - the path to the directory of the log files to be tailed.
* @param regex
* - regular expression for file names to be tailed.
* @param samplingDelay
* - the time delay between sampling of files.
*/
public RollingFileAppenderTailer(final String dir, final String regex, final long samplingDelay) {
this.logsDirectory = dir;
this.regex = regex;
}
/**
* Start a new tailer on a predetermined folder.
*
* @param directory
* - logs directory to be tailed.
* @param regex
* - expected log file name format.
*/
public static void start(final String directory, final String regex) {
ScheduledExecutorService executor;
executor = Executors.newScheduledThreadPool(1);
executor.scheduleWithFixedDelay(new RollingFileAppenderTailer(directory, regex), 0, DEFAULT_SAMPLING_DELAY,
TimeUnit.MILLISECONDS);
}
/**
* Start a new tailer on a predetermined folder.
*
* @param directory
* - logs directory to be tailed.
* @param regex
* - expected log file name format.
* @param samplingDelay .
*/
public static void start(final String directory, final String regex, final long samplingDelay) {
ScheduledExecutorService executor;
executor = Executors.newScheduledThreadPool(1);
executor.scheduleWithFixedDelay(new RollingFileAppenderTailer(directory, regex), 0, samplingDelay,
TimeUnit.MILLISECONDS);
}
/*****************
* The synchronized statement is used to make sure that only one invocation of the tailer will execute at any one
* time. Within the context of the USM, the tailer runs in an async task every 5 seconds, but may be called on a
* separate thread as well. For instance, if the USM fails to start the underlying process, it needs to dump the
* file contents before continuing.
*/
@Override
public synchronized void run() {
try {
getLogFilesMap(logFileMap);
for (final String key : logFileMap.keySet()) {
if (logFileMap.get(key).wasModified()) {
final String lines = logFileMap.get(key).readLines();
final String[] seporatedLines = lineSplitPattern.split(lines);
for (final String line : seporatedLines) {
handler.handleLine(key, line);
// logger.info(line);
}
}
}
} catch (final Exception e) {
logger.warning("Exception thrown: " + e.getMessage());
}
}
/**
* Scans the folder for new files added to the logs folder. If a new file that is not contained in the map is found,
* it is added to the map. If a file no longer exists, it will be taken out of the map.
*
* @param logFileList
*/
private void getLogFilesMap(final Map<String, RollingFileReader> logFileMap) {
final File folder = new File(logsDirectory);
// Get list of files according to regex.
final File[] files = folder.listFiles(new FilenameFilter() {
@Override
public boolean accept(final File dir, final String name) {
return java.util.regex.Pattern.matches(regex, name);
}
});
// add newly created files if exist.
for (final File file : files) {
if (!logFileMap.containsKey(file.getName())) {
logFileMap.put(file.getName(), new RollingFileReader(file));
}
}
// remove files that no longer exist.
final Iterator<RollingFileReader> iterator = logFileMap.values().iterator();
while (iterator.hasNext()) {
final RollingFileReader next = iterator.next();
if (!next.exists()) {
iterator.remove();
}
}
}
}