package org.araqne.log.api;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
public class TimeRollingLogWriter extends AbstractLogger implements LoggerRegistryEventListener {
private final org.slf4j.Logger slog = org.slf4j.LoggerFactory.getLogger(TimeRollingLogWriter.class);
private final String filePath;
private final String rotateInterval;
private final String charsetName;
private final boolean addCR;
/**
* file name postfix
*/
private SimpleDateFormat dateFormat;
private LoggerRegistry loggerRegistry;
/**
* full name of data source logger
*/
private String loggerName;
private BufferedOutputStream bos;
private FileOutputStream fos;
private boolean noRollingMode;
private String currentFilePath;
private Receiver receiver = new Receiver();
public TimeRollingLogWriter(LoggerSpecification spec, LoggerFactory factory, LoggerRegistry loggerRegistry) {
super(spec, factory);
this.loggerRegistry = loggerRegistry;
Map<String, String> config = spec.getConfig();
this.loggerName = config.get("source_logger");
this.filePath = config.get("file_path");
this.rotateInterval = config.get("rotate_interval");
if (rotateInterval.equals("day")) {
dateFormat = new SimpleDateFormat("yyyy-MM-dd");
} else if (rotateInterval.equals("hour")) {
dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH");
}
String s = config.get("charset");
this.charsetName = s != null ? s : "utf-8";
String osName = System.getProperty("os.name");
this.addCR = osName != null && osName.toLowerCase().contains("windows");
}
public void flush() {
if (bos != null) {
try {
bos.flush();
fos.getFD().sync();
} catch (IOException e) {
}
}
}
@Override
protected void onStart(LoggerStartReason reason) {
String targetPath = filePath;
if (dateFormat != null)
targetPath += "." + dateFormat.format(new Date());
ensureOpen(new File(targetPath));
loggerRegistry.addListener(this);
Logger logger = loggerRegistry.getLogger(loggerName);
if (logger != null) {
slog.debug("araqne log api: connect pipe to source logger [{}]", loggerName);
logger.addLogPipe(receiver);
} else
slog.debug("araqne log api: source logger [{}] not found", loggerName);
}
@Override
protected void onStop(LoggerStopReason reason) {
ensureClose();
try {
if (loggerRegistry != null) {
Logger logger = loggerRegistry.getLogger(loggerName);
if (logger != null) {
slog.debug("araqne log api: disconnect pipe from source logger [{}]", loggerName);
logger.removeLogPipe(receiver);
}
loggerRegistry.removeListener(this);
}
} catch (Throwable t) {
slog.debug("araqne log api: cannot remove logger [" + getFullName() + "] from registry", t);
}
}
private void ensureOpen(File f) {
if (f.getParentFile().mkdirs())
slog.info("araqne log api: created parent directory [{}] by time rolling log file logger", f.getParentFile()
.getAbsolutePath());
try {
fos = new FileOutputStream(f, true);
bos = new BufferedOutputStream(fos);
currentFilePath = f.getAbsolutePath();
} catch (IOException e) {
throw new IllegalStateException("cannot open time rolling logger [" + getFullName() + "]", e);
}
}
private void ensureClose() {
slog.debug("araqne log api: closing output file of time rolling logger [{}]", getFullName());
try {
if (bos != null) {
bos.close();
bos = null;
}
} catch (Throwable t) {
}
try {
if (fos != null) {
fos.close();
fos = null;
}
} catch (Throwable t) {
}
}
@Override
public boolean isPassive() {
return true;
}
@Override
protected void runOnce() {
}
@Override
public void loggerAdded(Logger logger) {
if (logger.getFullName().equals(loggerName)) {
slog.debug("araqne log api: source logger [{}] loaded", loggerName);
logger.addLogPipe(receiver);
}
}
@Override
public void loggerRemoved(Logger logger) {
if (logger.getFullName().equals(loggerName)) {
slog.debug("araqne log api: source logger [{}] unloaded", loggerName);
logger.removeLogPipe(receiver);
}
}
private String buildLine(Log log) {
Map<String, Object> params = log.getParams();
String line = (String) params.get("line");
if (line != null)
return line;
int i = 0;
StringBuilder sb = new StringBuilder(8096);
for (String key : params.keySet()) {
if (i++ != 0)
sb.append(", ");
sb.append(key);
sb.append("=");
sb.append("\"");
Object value = params.get(key);
sb.append(value == null ? "" : escape(value.toString()));
sb.append("\"");
}
return sb.toString();
}
private static String escape(String s) {
return s.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"");
}
private boolean rollFile(String newPath) {
// close file stream, rename and reopen
ensureClose();
ensureOpen(new File(newPath));
return true;
}
private class Receiver extends AbstractLogPipe {
@Override
public void onLog(Logger logger, Log log) {
Map<String, Object> params = log.getParams();
write(new SimpleLog(log.getDate(), getFullName(), params));
String targetPath = filePath;
if (dateFormat != null) {
targetPath = filePath + "." + dateFormat.format(log.getDate());
}
try {
String line = buildLine(log);
byte[] b = line.getBytes(charsetName);
if (!currentFilePath.equals(targetPath)) {
if (!noRollingMode && !rollFile(targetPath)) {
noRollingMode = true;
slog.error(
"araqne log api: other process hold log file more than 10min, turn logger [{}] to non-rolling mode",
logger.getFullName());
}
}
bos.write(b);
if (addCR)
bos.write('\r');
bos.write('\n');
} catch (Throwable t) {
slog.debug("araqne log api: cannot write rolling log file, logger [" + logger.getFullName() + "]", t);
}
}
}
}