package org.araqne.log.api;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
public class GzipDirectoryWatchLogger extends AbstractLogger implements Reconfigurable {
private final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(GzipDirectoryWatchLogger.class);
public GzipDirectoryWatchLogger(LoggerSpecification spec, LoggerFactory factory) {
super(spec, factory);
}
@Override
public void onConfigChange(Map<String, String> oldConfigs, Map<String, String> newConfigs) {
if (!oldConfigs.get("base_path").equals(newConfigs.get("base_path"))
|| !oldConfigs.get("filename_pattern").equals(newConfigs.get("filename_pattern"))) {
setStates(new HashMap<String, Object>());
}
}
@Override
protected void runOnce() {
Map<String, String> configs = getConfigs();
String basePath = configs.get("base_path");
Pattern fileNamePattern = Pattern.compile(configs.get("filename_pattern"));
boolean isDeleteFile = false;
if (configs.containsKey("is_delete"))
isDeleteFile = Boolean.parseBoolean(getConfigs().get("is_delete"));
List<String> logFiles = FileUtils.matchFiles(basePath, fileNamePattern);
Map<String, LastPosition> lastPositions = LastPositionHelper.deserialize(getStates());
removeStatesOfDeletedFiles(lastPositions, logFiles);
try {
for (String path : logFiles) {
LoggerStatus status = getStatus();
if (status == LoggerStatus.Stopping || status == LoggerStatus.Stopped)
break;
processFile(lastPositions, path, isDeleteFile, fileNamePattern);
}
} finally {
setStates(LastPositionHelper.serialize(lastPositions));
}
}
private void removeStatesOfDeletedFiles(Map<String, LastPosition> lastPositions, List<String> currentFiles) {
Set<String> currentSet = new HashSet<String>(currentFiles);
for (String path : new ArrayList<String>(lastPositions.keySet())) {
if (!currentSet.contains(path))
lastPositions.remove(path);
}
}
protected void processFile(Map<String, LastPosition> lastPositions, String path, boolean isDeleteFile, Pattern fileNamePattern) {
LastPosition position = new LastPosition(path);
FileInputStream fis = null;
GZIPInputStream gis = null;
try {
// skip previous read part
long offset = 0;
LastPosition oldPosition = lastPositions.get(path);
if (oldPosition != null) {
offset = oldPosition.getPosition();
position.setPosition(offset);
position.setLastSeen(oldPosition.getLastSeen());
logger.trace("araqne log api: target gzip file [{}] skip offset [{}]", path, offset);
}
if (offset == -1)
return;
File file = new File(path);
fis = new FileInputStream(file);
gis = new GZIPInputStream(fis);
Receiver receiver = new Receiver(position, getConfigs().get("file_tag"), file.getName());
MultilineLogExtractor extractor = MultilineLogExtractor.build(this, receiver);
String dateFromFileName = getDateFromFileName(path, fileNamePattern);
extractor.extract(gis, new AtomicLong(), dateFromFileName);
position.setPosition(-1);
} catch (EOFException e) {
// abnormal end of content (e.g. missing new line), but read
// completed successfully
position.setPosition(-1);
} catch (FileNotFoundException e) {
logger.trace("araqne log api: [{}] logger read failure: file not found: {}", getName(), e.getMessage());
position.setPosition(-1);
} catch (Throwable e) {
logger.error("araqne log api: [" + getName() + "] logger read error", e);
} finally {
FileUtils.ensureClose(gis);
FileUtils.ensureClose(fis);
lastPositions.put(path, position);
if (isDeleteFile && position.getPosition() == -1 && new File(path).delete()) {
lastPositions.remove(path);
logger.trace("araqne log api: deleted gzip file [{}]", path);
}
}
}
private String getDateFromFileName(String path, Pattern fileNamePattern) {
String dateFromFileName = null;
Matcher fileNameDateMatcher = fileNamePattern.matcher(path);
if (fileNameDateMatcher.find()) {
int fileNameGroupCount = fileNameDateMatcher.groupCount();
if (fileNameGroupCount > 0) {
StringBuffer sb = new StringBuffer();
for (int i = 1; i <= fileNameGroupCount; ++i) {
sb.append(fileNameDateMatcher.group(i));
}
dateFromFileName = sb.toString();
}
}
return dateFromFileName;
}
private class Receiver extends AbstractLogPipe {
private long offset;
private LastPosition position;
private String fileTag;
private String fileName;
public Receiver(LastPosition position, String fileTag, String fileName) {
this.position = position;
this.fileTag = fileTag;
this.fileName = fileName;
}
@Override
public void onLog(Logger logger, Log log) {
if (fileTag != null)
log.getParams().put(fileTag, fileName);
if (position.getPosition() < offset)
write(log);
position.setPosition(position.getPosition() + 1);
}
@Override
public void onLogBatch(Logger logger, Log[] logs) {
List<Log> filtered = new ArrayList<Log>();
if (fileTag != null) {
for (Log log : logs) {
log.getParams().put(fileTag, fileName);
}
}
for (Log log : logs) {
if (log != null) {
if (position.getPosition() >= offset)
filtered.add(log);
position.setPosition(position.getPosition() + 1);
}
}
writeBatch(filtered.toArray(new Log[0]));
}
}
}