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.util.Queue;
/**
* Log writer thread that pulls log records from the LogManager and writes them to
* the log. This also periodically instructs the LogManager to reread its config
* file.
*
* @since 0.9.19 pulled from FileLogWriter so Android may extend; renamed from LogWriterBase in 0.9.26
*/
abstract class LogWriter implements Runnable {
/** every 10 seconds? why? Just have the gui force a reread after a change?? */
private final static long CONFIG_READ_INTERVAL = 50 * 1000;
final static long FLUSH_INTERVAL = 29 * 1000;
private final static long MIN_FLUSH_INTERVAL = 2*1000;
private final static long MAX_FLUSH_INTERVAL = 5*60*1000;
private long _lastReadConfig;
protected final LogManager _manager;
protected volatile boolean _write;
private LogRecord _last;
private long _firstTimestamp;
// ms
private volatile long _flushInterval = FLUSH_INTERVAL;
public LogWriter(LogManager manager) {
_manager = manager;
_lastReadConfig = Clock.getInstance().now();
}
/**
* File may not exist or have old logs in it if not opened yet
* @return non-null
*/
public abstract String currentFile();
/**
* Write the provided LogRecord to the writer.
* @param rec the LogRecord to write.
* @param formatted a String pre-formatted from rec, may be ignored.
*/
protected abstract void writeRecord(LogRecord rec, String formatted);
/**
* Write a single String verbatim to the writer.
* @param priority the level to log the line at.
* @param line the String to write.
*/
protected abstract void writeRecord(int priority, String line);
protected abstract void flushWriter();
protected abstract void closeWriter();
public void stopWriting() {
_write = false;
}
/**
* @param interval ms
* @since 0.9.18
*/
public void setFlushInterval(long interval) {
_flushInterval = Math.min(MAX_FLUSH_INTERVAL, Math.max(MIN_FLUSH_INTERVAL, interval));
}
public void run() {
_write = true;
// don't bother on Android
final boolean shouldReadConfig = !SystemVersion.isAndroid();
try {
while (_write) {
flushRecords();
if (_write && shouldReadConfig)
rereadConfig();
}
} catch (RuntimeException e) {
System.err.println("Error writing the log: " + e);
e.printStackTrace();
}
closeWriter();
}
public void flushRecords() { flushRecords(true); }
public void flushRecords(boolean shouldWait) {
try {
// zero copy, drain the manager queue directly
Queue<LogRecord> records = _manager.getQueue();
if (records == null) return;
if (!records.isEmpty()) {
if (_last != null && _firstTimestamp < _manager.getContext().clock().now() - 30*60*1000)
_last = null;
LogRecord rec;
int dupCount = 0;
while ((rec = records.poll()) != null) {
if (_manager.shouldDropDuplicates() && rec.equals(_last)) {
dupCount++;
} else {
if (dupCount > 0) {
writeDupMessage(dupCount, _last);
dupCount = 0;
}
writeRecord(rec);
_firstTimestamp = rec.getDate();
}
_last = rec;
}
if (dupCount > 0) {
writeDupMessage(dupCount, _last);
}
flushWriter();
}
} catch (Throwable t) {
t.printStackTrace();
} finally {
if (shouldWait) {
try {
synchronized (this) {
this.wait(_flushInterval);
}
} catch (InterruptedException ie) { // nop
}
}
}
}
/**
* Write a msg with the date stamp of the last duplicate
* @since 0.9.21
*/
private void writeDupMessage(int dupCount, LogRecord lastRecord) {
String dmsg = dupMessage(dupCount, lastRecord, false);
writeRecord(lastRecord.getPriority(), dmsg);
if (_manager.getDisplayOnScreenLevel() <= lastRecord.getPriority() && _manager.displayOnScreen())
System.out.print(dmsg);
dmsg = dupMessage(dupCount, lastRecord, true);
_manager.getBuffer().add(dmsg);
if (lastRecord.getPriority() >= Log.CRIT)
_manager.getBuffer().addCritical(dmsg);
}
/**
* Return a msg with the date stamp of the last duplicate
* @since 0.9.3
*/
private String dupMessage(int dupCount, LogRecord lastRecord, boolean reverse) {
String arrows = reverse ? (SystemVersion.isAndroid() ? "vvv" : "↓↓↓") : "^^^";
return LogRecordFormatter.getWhen(_manager, lastRecord) + ' ' + arrows + ' ' +
_t(dupCount, "1 similar message omitted", "{0} similar messages omitted") + ' ' + arrows +
LogRecordFormatter.NL;
}
private static final String BUNDLE_NAME = "net.i2p.router.web.messages";
/**
* gettext
* @since 0.9.3
*/
private String _t(int a, String b, String c) {
return Translate.getString(a, b, c, _manager.getContext(), BUNDLE_NAME);
}
private void rereadConfig() {
long now = Clock.getInstance().now();
if (now - _lastReadConfig > CONFIG_READ_INTERVAL) {
_manager.rereadConfig();
_lastReadConfig = now;
}
}
private void writeRecord(LogRecord rec) {
String val = LogRecordFormatter.formatRecord(_manager, rec, true);
writeRecord(rec, val);
// we always add to the console buffer, but only sometimes write to stdout
_manager.getBuffer().add(val);
if (rec.getPriority() >= Log.CRIT)
_manager.getBuffer().addCritical(val);
if (_manager.getDisplayOnScreenLevel() <= rec.getPriority()) {
if (_manager.displayOnScreen()) {
// wrapper and android logs already do time stamps, so reformat without the date
if (_manager.getContext().hasWrapper() || SystemVersion.isAndroid())
System.out.print(LogRecordFormatter.formatRecord(_manager, rec, false));
else
System.out.print(val);
}
}
}
}