package net.i2p.util;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
/**
* File-based log writer thread that pulls log records from the LogManager,
* writes them to the current logfile, and rotates the logs as necessary.
*
* @since 0.9.26 moved from LogWriter
*/
class FileLogWriter extends LogWriter {
// volatile as it changes on log file rotation
private volatile Writer _currentOut;
private int _rotationNum = -1;
private File _currentFile;
private long _numBytesInCurrentFile;
private static final int MAX_DISKFULL_MESSAGES = 8;
private int _diskFullMessageCount;
public FileLogWriter(LogManager manager) {
super(manager);
}
/**
* File may not exist or have old logs in it if not opened yet
* @return non-null
*/
public synchronized String currentFile() {
if (_currentFile != null)
return _currentFile.getAbsolutePath();
String rv = getNextFile().getAbsolutePath();
// so it doesn't increment every time we call this
_rotationNum = -1;
return rv;
}
protected void writeRecord(LogRecord rec, String formatted) {
writeRecord(rec.getPriority(), formatted);
}
protected synchronized void writeRecord(int priority, String val) {
if (val == null) return;
if (_currentOut == null) {
rotateFile();
if (_currentOut == null)
return; // hosed
}
try {
_currentOut.write(val);
// may be a little off if a lot of multi-byte chars, but unlikely
_numBytesInCurrentFile += val.length();
} catch (Throwable t) {
if (!_write)
return;
if (++_diskFullMessageCount < MAX_DISKFULL_MESSAGES)
System.err.println("Error writing log, disk full? " + t);
//t.printStackTrace();
}
if (_numBytesInCurrentFile >= _manager.getFileSize()) {
rotateFile();
}
}
/**
* @since 0.9.19
*/
protected void flushWriter() {
try {
if (_currentOut != null)
_currentOut.flush();
} catch (IOException ioe) {
if (_write && ++_diskFullMessageCount < MAX_DISKFULL_MESSAGES)
System.err.println("Error writing the router log - disk full? " + ioe);
}
}
/**
* @since 0.9.19 renamed from closeFile()
*/
protected void closeWriter() {
Writer out = _currentOut;
if (out != null) {
try {
out.close();
} catch (IOException ioe) {}
}
}
/**
* Rotate to the next file (or the first file if this is the first call)
*
* Caller must synch
*/
private void rotateFile() {
File f = getNextFile();
_currentFile = f;
_numBytesInCurrentFile = 0;
File parent = f.getParentFile();
if (parent != null) {
if (!parent.exists()) {
File sd = new SecureDirectory(parent.getAbsolutePath());
boolean ok = sd.mkdirs();
if (!ok) {
System.err.println("Unable to create the parent directory: " + parent.getAbsolutePath());
//System.exit(0);
}
}
if (!parent.isDirectory()) {
System.err.println("Cannot put the logs in a subdirectory of a plain file: " + f.getAbsolutePath());
//System.exit(0);
}
}
closeWriter();
try {
_currentOut = new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(f), "UTF-8"));
} catch (IOException ioe) {
if (++_diskFullMessageCount < MAX_DISKFULL_MESSAGES)
System.err.println("Error creating log file [" + f.getAbsolutePath() + "]" + ioe);
}
}
/**
* Get the next file in the rotation
*
* Caller must synch
*/
private File getNextFile() {
String pattern = _manager.getBaseLogfilename();
File f = new File(pattern);
File base = null;
if (!f.isAbsolute())
base = _manager.getContext().getLogDir();
if ( (pattern.indexOf('#') < 0) && (pattern.indexOf('@') <= 0) ) {
if (base != null)
return new File(base, pattern);
else
return f;
}
int max = _manager.getRotationLimit();
if (_rotationNum == -1) {
return getFirstFile(base, pattern, max);
}
// we're in rotation, just go to the next
_rotationNum++;
if (_rotationNum > max) _rotationNum = 0;
String newf = replace(pattern, _rotationNum);
if (base != null)
return new File(base, newf);
return new File(newf);
}
/**
* Retrieve the first file, updating the rotation number accordingly
*
* Caller must synch
*/
private File getFirstFile(File base, String pattern, int max) {
for (int i = 0; i < max; i++) {
File f;
if (base != null)
f = new File(base, replace(pattern, i));
else
f = new File(replace(pattern, i));
if (!f.exists()) {
_rotationNum = i;
return f;
}
}
// all exist, pick the oldest to replace
File oldest = null;
for (int i = 0; i < max; i++) {
File f;
if (base != null)
f = new File(base, replace(pattern, i));
else
f = new File(replace(pattern, i));
if (oldest == null) {
oldest = f;
} else {
if (f.lastModified() < oldest.lastModified()) {
_rotationNum = i;
oldest = f;
}
}
}
return oldest;
}
private static final String replace(String pattern, int num) {
char c[] = pattern.toCharArray();
StringBuilder buf = new StringBuilder();
for (int i = 0; i < c.length; i++) {
if ( (c[i] != '#') && (c[i] != '@') )
buf.append(c[i]);
else
buf.append(num);
}
return buf.toString();
}
}