forked from I2P_Developers/i2p.i2p
Rename LogWriter -> FileLogWriter, LogWriterBase -> LogWriter
This commit is contained in:
218
core/java/src/net/i2p/util/FileLogWriter.java
Normal file
218
core/java/src/net/i2p/util/FileLogWriter.java
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
@@ -163,7 +163,7 @@ public class LogManager implements Flushable {
|
|||||||
// yeah, this doesn't always work, _writer should be volatile
|
// yeah, this doesn't always work, _writer should be volatile
|
||||||
if (_writer != null)
|
if (_writer != null)
|
||||||
return;
|
return;
|
||||||
_writer = new LogWriter(this);
|
_writer = new FileLogWriter(this);
|
||||||
_writer.setFlushInterval(_flushInterval * 1000);
|
_writer.setFlushInterval(_flushInterval * 1000);
|
||||||
// if you enable logging in I2PThread again, you MUST change this back to Thread
|
// if you enable logging in I2PThread again, you MUST change this back to Thread
|
||||||
Thread t = new I2PThread(_writer, "LogWriter");
|
Thread t = new I2PThread(_writer, "LogWriter");
|
||||||
|
@@ -9,210 +9,182 @@ package net.i2p.util;
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.util.Queue;
|
||||||
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,
|
* Log writer thread that pulls log records from the LogManager and writes them to
|
||||||
* writes them to the current logfile, and rotates the logs as necessary.
|
* the log. This also periodically instructs the LogManager to reread its config
|
||||||
|
* file.
|
||||||
*
|
*
|
||||||
|
* @since 0.9.19 pulled from FileLogWriter so Android may extend
|
||||||
*/
|
*/
|
||||||
class LogWriter extends LogWriterBase {
|
abstract class LogWriter implements Runnable {
|
||||||
// volatile as it changes on log file rotation
|
/** every 10 seconds? why? Just have the gui force a reread after a change?? */
|
||||||
private volatile Writer _currentOut;
|
private final static long CONFIG_READ_INTERVAL = 50 * 1000;
|
||||||
private int _rotationNum = -1;
|
final static long FLUSH_INTERVAL = 29 * 1000;
|
||||||
private File _currentFile;
|
private final static long MIN_FLUSH_INTERVAL = 2*1000;
|
||||||
private long _numBytesInCurrentFile;
|
private final static long MAX_FLUSH_INTERVAL = 5*60*1000;
|
||||||
|
private long _lastReadConfig;
|
||||||
|
protected final LogManager _manager;
|
||||||
|
|
||||||
private static final int MAX_DISKFULL_MESSAGES = 8;
|
protected volatile boolean _write;
|
||||||
private int _diskFullMessageCount;
|
private LogRecord _last;
|
||||||
|
// ms
|
||||||
|
private volatile long _flushInterval = FLUSH_INTERVAL;
|
||||||
|
|
||||||
public LogWriter(LogManager manager) {
|
public LogWriter(LogManager manager) {
|
||||||
super(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 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File may not exist or have old logs in it if not opened yet
|
* @param interval ms
|
||||||
|
* @since 0.9.18
|
||||||
*/
|
*/
|
||||||
public synchronized String currentFile() {
|
public void setFlushInterval(long interval) {
|
||||||
if (_currentFile != null)
|
_flushInterval = Math.min(MAX_FLUSH_INTERVAL, Math.max(MIN_FLUSH_INTERVAL, interval));
|
||||||
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) {
|
public void run() {
|
||||||
writeRecord(rec.getPriority(), formatted);
|
_write = true;
|
||||||
}
|
// don't bother on Android
|
||||||
|
final boolean shouldReadConfig = !SystemVersion.isAndroid();
|
||||||
protected synchronized void writeRecord(int priority, String val) {
|
|
||||||
if (val == null) return;
|
|
||||||
if (_currentOut == null) {
|
|
||||||
rotateFile();
|
|
||||||
if (_currentOut == null)
|
|
||||||
return; // hosed
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_currentOut.write(val);
|
while (_write) {
|
||||||
// may be a little off if a lot of multi-byte chars, but unlikely
|
flushRecords();
|
||||||
_numBytesInCurrentFile += val.length();
|
if (_write && shouldReadConfig)
|
||||||
} catch (Throwable t) {
|
rereadConfig();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
System.err.println("Error writing the log: " + e);
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
closeWriter();
|
closeWriter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flushRecords() { flushRecords(true); }
|
||||||
|
|
||||||
|
public void flushRecords(boolean shouldWait) {
|
||||||
try {
|
try {
|
||||||
_currentOut = new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(f), "UTF-8"));
|
// zero copy, drain the manager queue directly
|
||||||
} catch (IOException ioe) {
|
Queue<LogRecord> records = _manager.getQueue();
|
||||||
if (++_diskFullMessageCount < MAX_DISKFULL_MESSAGES)
|
if (records == null) return;
|
||||||
System.err.println("Error creating log file [" + f.getAbsolutePath() + "]" + ioe);
|
if (!records.isEmpty()) {
|
||||||
}
|
if (_last != null && _last.getDate() < _manager.getContext().clock().now() - 30*60*1000)
|
||||||
}
|
_last = null;
|
||||||
|
LogRecord rec;
|
||||||
/**
|
int dupCount = 0;
|
||||||
* Get the next file in the rotation
|
while ((rec = records.poll()) != null) {
|
||||||
*
|
if (_manager.shouldDropDuplicates() && rec.equals(_last)) {
|
||||||
* Caller must synch
|
dupCount++;
|
||||||
*/
|
} else {
|
||||||
private File getNextFile() {
|
if (dupCount > 0) {
|
||||||
String pattern = _manager.getBaseLogfilename();
|
writeDupMessage(dupCount, _last);
|
||||||
File f = new File(pattern);
|
dupCount = 0;
|
||||||
File base = null;
|
}
|
||||||
if (!f.isAbsolute())
|
writeRecord(rec);
|
||||||
base = _manager.getContext().getLogDir();
|
}
|
||||||
|
_last = rec;
|
||||||
if ( (pattern.indexOf('#') < 0) && (pattern.indexOf('@') <= 0) ) {
|
}
|
||||||
if (base != null)
|
if (dupCount > 0) {
|
||||||
return new File(base, pattern);
|
writeDupMessage(dupCount, _last);
|
||||||
else
|
}
|
||||||
return f;
|
flushWriter();
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
} catch (Throwable t) {
|
||||||
|
t.printStackTrace();
|
||||||
// all exist, pick the oldest to replace
|
} finally {
|
||||||
File oldest = null;
|
if (shouldWait) {
|
||||||
for (int i = 0; i < max; i++) {
|
try {
|
||||||
File f;
|
synchronized (this) {
|
||||||
if (base != null)
|
this.wait(_flushInterval);
|
||||||
f = new File(base, replace(pattern, i));
|
}
|
||||||
else
|
} catch (InterruptedException ie) { // nop
|
||||||
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();
|
* Write a msg with the date stamp of the last duplicate
|
||||||
StringBuilder buf = new StringBuilder();
|
* @since 0.9.21
|
||||||
for (int i = 0; i < c.length; i++) {
|
*/
|
||||||
if ( (c[i] != '#') && (c[i] != '@') )
|
private void writeDupMessage(int dupCount, LogRecord lastRecord) {
|
||||||
buf.append(c[i]);
|
String dmsg = dupMessage(dupCount, lastRecord, false);
|
||||||
else
|
writeRecord(lastRecord.getPriority(), dmsg);
|
||||||
buf.append(num);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return buf.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,190 +0,0 @@
|
|||||||
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 LogWriter so Android may extend
|
|
||||||
*/
|
|
||||||
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 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 && _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) {
|
|
||||||
writeDupMessage(dupCount, _last);
|
|
||||||
dupCount = 0;
|
|
||||||
}
|
|
||||||
writeRecord(rec);
|
|
||||||
}
|
|
||||||
_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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user