/*
* Copyright 2013 Eediom Inc.
*
* 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.araqne.log.api;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Date;
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 org.araqne.log.api.impl.FileUtils;
public class DirectoryWatchLogger extends AbstractLogger implements Reconfigurable {
private final org.slf4j.Logger slog = org.slf4j.LoggerFactory.getLogger(DirectoryWatchLogger.class.getName());
public DirectoryWatchLogger(LoggerSpecification spec, LoggerFactory factory) {
super(spec, factory);
File dataDir = new File(System.getProperty("araqne.data.dir"), "araqne-log-api");
dataDir.mkdirs();
// try migration at boot
File oldLastFile = getLastLogFile(dataDir);
if (oldLastFile.exists()) {
Map<String, LastPosition> lastPositions = LastPositionHelper.readLastPositions(oldLastFile);
setStates(LastPositionHelper.serialize(lastPositions));
oldLastFile.renameTo(new File(oldLastFile.getAbsolutePath() + ".migrated"));
}
}
@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"));
List<File> logFiles = FileUtils.matches(basePath, fileNamePattern);
Map<String, LastPosition> lastPositions = LastPositionHelper.deserialize(getStates());
for (File f : logFiles) {
processFile(lastPositions, f, fileNamePattern);
}
lastPositions = updateLastSeen(lastPositions, logFiles);
setStates(LastPositionHelper.serialize(lastPositions));
}
private Map<String, LastPosition> updateLastSeen(Map<String, LastPosition> lastPositions, List<File> logFiles) {
Map<String, LastPosition> updatedLastPositions = new HashMap<String, LastPosition>();
Set<String> filePaths = new HashSet<String>();
for (File f : logFiles)
filePaths.add(f.getAbsolutePath());
long currentTime = new Date().getTime();
for (String path : lastPositions.keySet()) {
LastPosition p = lastPositions.get(path);
if (p.getLastSeen() != null) {
long limitTime = p.getLastSeen().getTime() + 3600000L;
if (filePaths.contains(path)) {
p.setLastSeen(null);
} else if (limitTime <= currentTime) {
continue;
}
} else {
if (!filePaths.contains(path)) {
p.setLastSeen(new Date());
}
}
updatedLastPositions.put(path, p);
}
return updatedLastPositions;
}
protected void processFile(Map<String, LastPosition> lastPositions, File f, Pattern fileNamePattern) {
if (!f.canRead()) {
slog.debug("araqne log api: cannot read file [{}], logger [{}]", f.getAbsolutePath(), getFullName());
return;
}
FileInputStream is = null;
String path = f.getAbsolutePath();
try {
// get date pattern-matched string from filename
String dateFromFileName = null;
Matcher fileNameDateMatcher = fileNamePattern.matcher(f.getName());
while (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();
}
}
// skip previous read part
long offset = 0;
if (lastPositions.containsKey(path)) {
LastPosition inform = lastPositions.get(path);
offset = inform.getPosition();
slog.trace("araqne log api: target file [{}] skip offset [{}]", path, offset);
}
AtomicLong lastPosition = new AtomicLong(offset);
File file = new File(path);
if (file.length() <= offset)
return;
is = new FileInputStream(file);
is.skip(offset);
Receiver receiver = new Receiver(getConfigs().get("file_tag"), file.getName());
MultilineLogExtractor extractor = MultilineLogExtractor.build(this, receiver);
extractor.extract(is, lastPosition, dateFromFileName);
slog.debug("araqne log api: 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 (FileNotFoundException e) {
if (slog.isTraceEnabled())
slog.trace("araqne log api: [" + getName() + "] logger read failure: file not found: {}", e.getMessage());
} catch (Throwable e) {
slog.error("araqne log api: [" + getName() + "] logger read error", e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
}
protected File getLastLogFile(File dataDir) {
return new File(dataDir, "dirwatch-" + getName() + ".lastlog");
}
private class Receiver extends AbstractLogPipe {
private String fileTag;
private String fileName;
public Receiver(String fileTag, String fileName) {
this.fileTag = fileTag;
this.fileName = 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);
}
}
}