package org.araqne.log.api.nio;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.araqne.log.api.AbstractLogPipe;
import org.araqne.log.api.AbstractLogger;
import org.araqne.log.api.LastPosition;
import org.araqne.log.api.LastPositionHelper;
import org.araqne.log.api.Log;
import org.araqne.log.api.Logger;
import org.araqne.log.api.LoggerFactory;
import org.araqne.log.api.LoggerSpecification;
import org.araqne.log.api.MultilineLogExtractor;
import org.araqne.log.api.Reconfigurable;
public class NaiveRecursiveDirectoryWatchLogger extends AbstractLogger implements Reconfigurable {
private final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(NaiveRecursiveDirectoryWatchLogger.class);
private String basePath;
private Pattern fileNamePattern;
private Pattern dirPathPattern;
private boolean recursive;
private String fileTag;
private MultilineLogExtractor extractor;
private Receiver receiver = new Receiver();
public NaiveRecursiveDirectoryWatchLogger(LoggerSpecification spec, LoggerFactory factory) {
super(spec, factory);
if (logger.isDebugEnabled())
logger.debug("araqne-logapi-nio: recursive dirwatcher used recursive scan");
applyConfig();
}
private void applyConfig() {
basePath = getConfigs().get("base_path");
String fileNameRegex = getConfigs().get("filename_pattern");
fileNamePattern = Pattern.compile(fileNameRegex);
// optional
String dirNameRegex = getConfigs().get("dirpath_pattern");
if (dirNameRegex != null)
dirPathPattern = Pattern.compile(dirNameRegex);
extractor = new MultilineLogExtractor(this, receiver);
// optional
String dateExtractRegex = getConfigs().get("date_pattern");
if (dateExtractRegex != null)
extractor.setDateMatcher(Pattern.compile(dateExtractRegex).matcher(""));
// optional
String dateLocale = getConfigs().get("date_locale");
if (dateLocale == null)
dateLocale = "en";
// optional
String dateFormatString = getConfigs().get("date_format");
String timeZone = getConfigs().get("timezone");
if (dateFormatString != null)
extractor.setDateFormat(new SimpleDateFormat(dateFormatString, new Locale(dateLocale)), timeZone);
// optional
String newlogRegex = getConfigs().get("newlog_designator");
if (newlogRegex != null)
extractor.setBeginMatcher(Pattern.compile(newlogRegex).matcher(""));
String newlogEndRegex = getConfigs().get("newlog_end_designator");
if (newlogEndRegex != null)
extractor.setEndMatcher(Pattern.compile(newlogEndRegex).matcher(""));
// optional
String charset = getConfigs().get("charset");
if (charset == null)
charset = "utf-8";
// optional
String recursive = getConfigs().get("recursive");
this.recursive = ((recursive != null) && (recursive.compareToIgnoreCase("true") == 0));
// optional
this.fileTag = getConfigs().get("file_tag");
extractor.setCharset(charset);
}
@Override
protected void runOnce() {
Map<String, LastPosition> lastPositions = LastPositionHelper.deserialize(getStates());
List<File> files = getFiles();
if (files == null)
return;
Collections.sort(files);
try {
for (File f : files)
processFile(f, lastPositions);
} finally {
setStates(LastPositionHelper.serialize(lastPositions));
}
}
private void processFile(File f, Map<String, LastPosition> lastPositions) {
FileInputStream is = null;
try {
String path = f.getAbsolutePath();
long offset = 0;
if (lastPositions.containsKey(path)) {
LastPosition inform = lastPositions.get(path);
offset = inform.getPosition();
logger.trace("araqne-logapi-nio: target file [{}] skip offset [{}]", path, offset);
}
AtomicLong lastPosition = new AtomicLong(offset);
File file = new File(path);
if (file.length() <= offset)
return;
receiver.filename = file.getName();
is = new FileInputStream(file);
is.skip(offset);
extractor.extract(is, lastPosition, getDateString(f));
logger.debug("araqne-logapi-nio: updating file [{}] old position [{}] new last position [{}]", new Object[] { path,
offset, lastPosition.get() });
LastPosition inform = lastPositions.get(path);
if (inform == null) {
inform = new LastPosition(path);
}
inform.setPosition(lastPosition.get());
lastPositions.put(path, inform);
} catch (Throwable e) {
logger.error("araqne-logapi-nio: [" + getName() + "] logger read error", e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
}
private String getDateString(File f) {
StringBuffer sb = new StringBuffer(f.getAbsolutePath().length());
String dirPath = f.getParentFile().getAbsolutePath();
if (dirPathPattern != null) {
Matcher dirNameDateMatcher = dirPathPattern.matcher(dirPath);
while (dirNameDateMatcher.find()) {
int dirNameGroupCount = dirNameDateMatcher.groupCount();
if (dirNameGroupCount > 0) {
for (int i = 1; i <= dirNameGroupCount; ++i) {
sb.append(dirNameDateMatcher.group(i));
}
}
}
}
String fileName = f.getName();
Matcher fileNameDateMatcher = fileNamePattern.matcher(fileName);
while (fileNameDateMatcher.find()) {
int fileNameGroupCount = fileNameDateMatcher.groupCount();
if (fileNameGroupCount > 0) {
for (int i = 1; i <= fileNameGroupCount; ++i) {
sb.append(fileNameDateMatcher.group(i));
}
}
}
String date = sb.toString();
return date.isEmpty() ? null : date;
}
private List<File> getFiles() {
File base = new File(basePath);
if (!base.exists() || !base.isDirectory()) {
logger.debug("araqne logapi nio: invalid base path [{}]", basePath);
return null;
}
List<File> targetFiles = new ArrayList<File>();
for (File f : base.listFiles()) {
if (f.isDirectory())
targetFiles.addAll(getFiles(f));
else if (dirPathPattern == null || dirPathPattern.matcher(base.getAbsolutePath()).find())
targetFiles.addAll(getFiles(f));
}
return targetFiles;
}
private List<File> getFiles(File f) {
List<File> files = new ArrayList<File>();
if (f.isDirectory() && recursive) {
if (dirPathPattern == null || dirPathPattern.matcher(f.getAbsolutePath()).find()) {
for (File subFile : f.listFiles())
files.addAll(getFiles(subFile));
}
} else {
if (fileNamePattern.matcher(f.getName()).matches())
files.add(f);
}
return files;
}
private class Receiver extends AbstractLogPipe {
private String filename;
@Override
public void onLog(Logger logger, Log log) {
if (fileTag != null)
log.getParams().put(fileTag, filename);
write(log);
}
@Override
public void onLogBatch(Logger logger, Log[] logs) {
if (fileTag != null) {
for (Log log : logs) {
log.getParams().put(fileTag, filename);
}
}
writeBatch(logs);
}
}
@Override
public void onConfigChange(Map<String, String> oldConfigs, Map<String, String> newConfigs) {
if (isRunning())
throw new IllegalStateException("logger is running");
if (!oldConfigs.get("base_path").equals(newConfigs.get("base_path"))
|| !oldConfigs.get("filename_pattern").equals(newConfigs.get("filename_pattern"))) {
setStates(new HashMap<String, Object>());
}
applyConfig();
}
}