diff --git a/core/java/src/net/i2p/util/LogWriter.java b/core/java/src/net/i2p/util/LogWriter.java index 1d24e34ef45138de296eb4be2df26400698c9688..15c60e566b46d7ad4cd85566d5422ddc568a7f99 100644 --- a/core/java/src/net/i2p/util/LogWriter.java +++ b/core/java/src/net/i2p/util/LogWriter.java @@ -14,139 +14,24 @@ import java.io.File; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; -import java.util.Queue; /** - * Log writer thread that pulls log records from the LogManager, writes them to - * the current logfile, and rotates the logs as necessary. This also periodically - * instructs the LogManager to reread its config file. + * File-based log writer thread that pulls log records from the LogManager, + * writes them to the current logfile, and rotates the logs as necessary. * */ -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; - private long _numBytesInCurrentFile; +class LogWriter extends LogWriterBase { // volatile as it changes on log file rotation private volatile Writer _currentOut; private int _rotationNum = -1; private File _currentFile; - private final LogManager _manager; + private long _numBytesInCurrentFile; - private volatile boolean _write; private static final int MAX_DISKFULL_MESSAGES = 8; private int _diskFullMessageCount; - private LogRecord _last; - // ms - private volatile long _flushInterval = FLUSH_INTERVAL; - - public LogWriter(LogManager manager) { - _manager = manager; - _lastReadConfig = Clock.getInstance().now(); - } - - public void stopWriting() { - _write = false; - } - - /** - * @param 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; - try { - // Don't rotate and open until needed - //rotateFile(); - while (_write) { - flushRecords(); - if (_write) - rereadConfig(); - } - //System.err.println("Done writing"); - } catch (Exception e) { - System.err.println("Error writing the log: " + e); - e.printStackTrace(); - } - closeFile(); - } - - 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 && _last.getDate() < _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) { - writeRecord(dupMessage(dupCount, _last, false)); - _manager.getBuffer().add(dupMessage(dupCount, _last, true)); - dupCount = 0; - } - writeRecord(rec); - } - _last = rec; - } - if (dupCount > 0) { - writeRecord(dupMessage(dupCount, _last, false)); - _manager.getBuffer().add(dupMessage(dupCount, _last, true)); - } - 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); - } - } - } catch (Throwable t) { - t.printStackTrace(); - } finally { - if (shouldWait) { - try { - synchronized (this) { - this.wait(_flushInterval); - } - } catch (InterruptedException ie) { // nop - } - } - } - } - - /** - * 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 ? "↓↓↓" : "^^^"; - return LogRecordFormatter.getWhen(_manager, lastRecord) + ' ' + arrows + ' ' + - _(dupCount, "1 similar message omitted", "{0} similar messages omitted") + ' ' + arrows + '\n'; - } - - private static final String BUNDLE_NAME = "net.i2p.router.web.messages"; - /** - * gettext - * @since 0.9.3 - */ - private String _(int a, String b, String c) { - return Translate.getString(a, b, c, _manager.getContext(), BUNDLE_NAME); + public LogWriter(LogManager manager) { + super(manager); } /** @@ -161,34 +46,11 @@ class LogWriter implements Runnable { return rv; } - private void rereadConfig() { - long now = Clock.getInstance().now(); - if (now - _lastReadConfig > CONFIG_READ_INTERVAL) { - _manager.rereadConfig(); - _lastReadConfig = now; - } + protected void writeRecord(LogRecord rec, String formatted) { + writeRecord(formatted); } - private void writeRecord(LogRecord rec) { - String val = LogRecordFormatter.formatRecord(_manager, rec, true); - writeRecord(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 log already does time stamps, so reformat without the date - if (_manager.getContext().hasWrapper()) - System.out.print(LogRecordFormatter.formatRecord(_manager, rec, false)); - else - System.out.print(val); - } - } - } - - private synchronized void writeRecord(String val) { + protected synchronized void writeRecord(String val) { if (val == null) return; if (_currentOut == null) { rotateFile(); @@ -212,6 +74,25 @@ class LogWriter implements Runnable { } } + 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); + } + } + + 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) * @@ -236,7 +117,7 @@ class LogWriter implements Runnable { //System.exit(0); } } - closeFile(); + closeWriter(); try { _currentOut = new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(f), "UTF-8")); } catch (IOException ioe) { @@ -245,15 +126,6 @@ class LogWriter implements Runnable { } } - private void closeFile() { - Writer out = _currentOut; - if (out != null) { - try { - out.close(); - } catch (IOException ioe) {} - } - } - /** * Get the next file in the rotation * diff --git a/core/java/src/net/i2p/util/LogWriterBase.java b/core/java/src/net/i2p/util/LogWriterBase.java new file mode 100644 index 0000000000000000000000000000000000000000..7448a1862e660cc7fd8f864800f5c6477554329b --- /dev/null +++ b/core/java/src/net/i2p/util/LogWriterBase.java @@ -0,0 +1,172 @@ +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. + * + */ +abstract class LogWriterBase 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; + // ms + private volatile long _flushInterval = FLUSH_INTERVAL; + + public LogWriterBase(LogManager manager) { + _manager = manager; + _lastReadConfig = Clock.getInstance().now(); + } + + 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 line the String to write. + */ + protected abstract void writeRecord(String line); + protected abstract void flushWriter(); + protected abstract void closeWriter(); + + public void stopWriting() { + _write = false; + } + + /** + * @param 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; + try { + while (_write) { + flushRecords(); + if (_write) + rereadConfig(); + } + } catch (Exception 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 && _last.getDate() < _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) { + writeRecord(dupMessage(dupCount, _last, false)); + _manager.getBuffer().add(dupMessage(dupCount, _last, true)); + dupCount = 0; + } + writeRecord(rec); + } + _last = rec; + } + if (dupCount > 0) { + writeRecord(dupMessage(dupCount, _last, false)); + _manager.getBuffer().add(dupMessage(dupCount, _last, true)); + } + flushWriter(); + } + } catch (Throwable t) { + t.printStackTrace(); + } finally { + if (shouldWait) { + try { + synchronized (this) { + this.wait(_flushInterval); + } + } catch (InterruptedException ie) { // nop + } + } + } + } + + /** + * 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 ? "↓↓↓" : "^^^"; + return LogRecordFormatter.getWhen(_manager, lastRecord) + ' ' + arrows + ' ' + + _(dupCount, "1 similar message omitted", "{0} similar messages omitted") + ' ' + arrows + '\n'; + } + + private static final String BUNDLE_NAME = "net.i2p.router.web.messages"; + + /** + * gettext + * @since 0.9.3 + */ + private String _(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); + } + } + } +}