/*
* 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.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;
public class RollingLogWriter extends AbstractLogger implements LoggerRegistryEventListener {
private final org.slf4j.Logger slog = org.slf4j.LoggerFactory.getLogger(RollingLogWriter.class);
private final File file;
private final long maxFileSize;
private final int maxBackupIndex;
private final String charsetName;
private LoggerRegistry loggerRegistry;
/**
* full name of data source logger
*/
private String loggerName;
private BufferedOutputStream bos;
private FileOutputStream fos;
private long totalBytes;
private boolean noRollingMode;
private Receiver receiver = new Receiver();
public RollingLogWriter(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.file = new File(config.get("file_path"));
this.maxFileSize = Long.parseLong(config.get("max_file_size"));
String s = config.get("max_backup_index");
this.maxBackupIndex = s != null && !s.isEmpty() ? Integer.parseInt(s) : 1;
s = config.get("charset");
this.charsetName = s != null ? s : "utf-8";
}
public void flush() {
if (bos != null) {
try {
bos.flush();
fos.getFD().sync();
} catch (IOException e) {
}
}
}
@Override
protected void onStart(LoggerStartReason reason) {
ensureOpen();
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() {
if (file.getParentFile().mkdirs())
slog.info("araqne log api: created parent directory [{}] by rolling log file transformer", file.getParentFile()
.getAbsolutePath());
if (file.exists())
totalBytes = file.length();
try {
fos = new FileOutputStream(file, true);
bos = new BufferedOutputStream(fos);
} catch (IOException e) {
throw new IllegalStateException("cannot open rolling logger [" + getFullName() + "]", e);
}
}
private void ensureClose() {
slog.debug("araqne log api: closing output file of 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 boolean rollFile() {
for (int num = maxBackupIndex; num > 0; num--) {
File fromPath = new File(file.getAbsolutePath() + "." + num);
if (!fromPath.exists())
continue;
if (num == maxBackupIndex) {
int tryCount = 0;
while (!fromPath.delete()) {
try {
if (tryCount++ > 6000) {
slog.debug("araqne log api: failed to delete [{}] at logger [{}]", fromPath.getAbsolutePath(),
getFullName());
return false;
}
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
continue;
}
File toPath = new File(file.getAbsolutePath() + "." + (num + 1));
slog.debug("araqne log api: try to rename [{}] to [{}] at logger [{}]", new Object[] { fromPath.getAbsolutePath(),
toPath.getAbsolutePath(), getFullName() });
int tryCount = 0;
while (!fromPath.renameTo(toPath)) {
try {
if (tryCount++ > 6000) {
slog.debug("araqne log api: failed to rename [{}] to [{}] at logger [{}]",
new Object[] { fromPath.getAbsolutePath(), toPath.getAbsolutePath(), getFullName() });
return false;
}
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
// close file stream, rename and reopen
File toPath = new File(file.getAbsolutePath() + ".1");
ensureClose();
int tryCount = 0;
slog.debug("araqne log api: try to rename [{}] to [{}] at logger [{}]",
new Object[] { file.getAbsolutePath(), toPath.getAbsolutePath(), getFullName() });
while (!file.renameTo(toPath)) {
try {
if (tryCount++ > 6000) {
slog.debug("araqne log api: failed to rename [{}] to [{}] at logger [{}]",
new Object[] { file.getAbsolutePath(), toPath.getAbsolutePath(), getFullName() });
return false;
}
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
ensureOpen();
return true;
}
private class Receiver extends AbstractLogPipe {
@Override
public void onLog(Logger logger, Log log) {
Map<String, Object> params = log.getParams();
String line = (String) params.get("line");
if (line == null)
return;
write(new SimpleLog(log.getDate(), getFullName(), params));
try {
byte[] b = line.getBytes(charsetName);
if (maxFileSize < totalBytes + b.length) {
if (!noRollingMode && !rollFile()) {
noRollingMode = true;
slog.error(
"araqne log api: other process hold log file more than 10min, turn logger [{}] to non-rolling mode",
logger.getFullName());
}
totalBytes = b.length;
} else {
totalBytes += b.length;
}
bos.write(b);
bos.write('\n');
} catch (Throwable t) {
slog.debug("araqne log api: cannot write rolling log file, logger [" + logger.getFullName() + "]", t);
}
}
}
}