package winterwell.utils.reporting;
import java.io.Closeable;
import java.io.File;
import winterwell.utils.Utils;
import winterwell.utils.io.FileUtils;
import winterwell.utils.time.Dt;
import winterwell.utils.time.Time;
/**
* Pipe log reports out to a file.
* <p>
* Reports are written and flushed immediately. This is not the most efficient
* thing, but it guarantees that the log will not lose the reports leading up to
* a crash (ie. the important ones).
* <p>
* LogFile's stay alive until they are closed! Use {@link #close()} to remove
* this LogFile from the log listeners.
*
* @author daniel
* @testedby {@link LogFileTest}
*/
public class LogFile implements ILogListener, Closeable {
private final File file;
Time nextRotation;
int rotationHistory;
Dt rotationInterval;
/**
* Create a .log file named after the calling class. Will append if the file
* already exists.
* <p>
* This is a wrapper for {@link #LogFile(File)}.
*/
public LogFile() {
this(new File(Utils.getCaller().getClassName() + ".log"));
}
/**
* Create a log-listener and attach it to the Log.
*
* @param f
*/
public LogFile(File f) {
file = f;
if (file.getParentFile() != null) {
file.getParentFile().mkdirs();
}
Log.addListener(this);
}
/**
* Delete all log entries from the file. The file will still exist but it
* will be empty.
*/
public void clear() {
FileUtils.write(file, "");
}
/**
* Stop listening to log events
*/
@Override
public void close() {
Log.removeListener(this);
}
public File getFile() {
return file;
}
@Override
public void listen(Report report) {
if (nextRotation != null && nextRotation.isBefore(report.getTime())) {
rotateLogFiles();
}
// a single line for each report to make it easier to grep
String line = report.toString().replaceAll("[\r\n]", " ") + "\n";
// append to file (flushes immediately)
synchronized (file) {
FileUtils.append(line, file);
}
}
/**
* Move all the log files down one.
*/
private synchronized void rotateLogFiles() {
// advance the trigger
nextRotation = nextRotation.plus(rotationInterval);
// just nuke the current log?
if (rotationHistory < 1) {
FileUtils.delete(file);
return;
}
// rotate the old logs
for (int i = rotationHistory - 1; i != 0; i--) {
File src = new File(file.getAbsolutePath() + "." + i);
File dest = new File(file.getAbsolutePath() + "." + (i + 1));
if (src.exists()) {
FileUtils.move(src, dest);
} else {
FileUtils.delete(dest);
}
}
// move the current log
File src = file;
File dest = new File(file.getAbsolutePath() + ".1");
if (src.exists()) {
FileUtils.move(src, dest);
}
}
/**
* By default, this class builds one giant log file. If this is set, logs
* will get rotated - but only if this JVM keeps running for long enough!
*
* @param interval
* How often to rotate
* @param history
* How many old log files to keep. 0 means just the current one.
* @testedby {@link LogFileTest#testRotation()}
*/
public void setLogRotation(Dt interval, int history) {
this.rotationInterval = interval;
this.rotationHistory = history;
// FIXME how do we get the file created time?
Time created = file.exists() ? new Time(file.lastModified())
: new Time();
nextRotation = created.plus(interval);
}
@Override
public String toString() {
return "LogFile:" + file.getAbsolutePath();
}
}