Changeset 03e188b5
- Timestamp:
- May 28, 2016 10:51:44 PM (5 years ago)
- Branches:
- master
- Children:
- e969213
- Parents:
- 8807787
- Location:
- core/java/src/net/i2p/util
- Files:
-
- 1 added
- 1 deleted
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
core/java/src/net/i2p/util/LogManager.java
r8807787 r03e188b5 164 164 if (_writer != null) 165 165 return; 166 _writer = new LogWriter(this);166 _writer = new FileLogWriter(this); 167 167 _writer.setFlushInterval(_flushInterval * 1000); 168 168 // if you enable logging in I2PThread again, you MUST change this back to Thread -
core/java/src/net/i2p/util/LogWriter.java
r8807787 r03e188b5 10 10 */ 11 11 12 import java.io.BufferedWriter; 13 import java.io.File; 14 import java.io.IOException; 15 import java.io.OutputStreamWriter; 16 import java.io.Writer; 12 import java.util.Queue; 17 13 18 14 /** 19 * File-based log writer thread that pulls log records from the LogManager, 20 * writes them to the current logfile, and rotates the logs as necessary. 15 * Log writer thread that pulls log records from the LogManager and writes them to 16 * the log. This also periodically instructs the LogManager to reread its config 17 * file. 21 18 * 19 * @since 0.9.19 pulled from FileLogWriter so Android may extend 22 20 */ 23 class LogWriter extends LogWriterBase { 24 // volatile as it changes on log file rotation 25 private volatile Writer _currentOut; 26 private int _rotationNum = -1; 27 private File _currentFile; 28 private long _numBytesInCurrentFile; 21 abstract class LogWriter implements Runnable { 22 /** every 10 seconds? why? Just have the gui force a reread after a change?? */ 23 private final static long CONFIG_READ_INTERVAL = 50 * 1000; 24 final static long FLUSH_INTERVAL = 29 * 1000; 25 private final static long MIN_FLUSH_INTERVAL = 2*1000; 26 private final static long MAX_FLUSH_INTERVAL = 5*60*1000; 27 private long _lastReadConfig; 28 protected final LogManager _manager; 29 29 30 private static final int MAX_DISKFULL_MESSAGES = 8; 31 private int _diskFullMessageCount; 30 protected volatile boolean _write; 31 private LogRecord _last; 32 // ms 33 private volatile long _flushInterval = FLUSH_INTERVAL; 32 34 33 35 public LogWriter(LogManager manager) { 34 super(manager); 36 _manager = manager; 37 _lastReadConfig = Clock.getInstance().now(); 38 } 39 40 public abstract String currentFile(); 41 /** 42 * Write the provided LogRecord to the writer. 43 * @param rec the LogRecord to write. 44 * @param formatted a String pre-formatted from rec, may be ignored. 45 */ 46 protected abstract void writeRecord(LogRecord rec, String formatted); 47 /** 48 * Write a single String verbatim to the writer. 49 * @param priority the level to log the line at. 50 * @param line the String to write. 51 */ 52 protected abstract void writeRecord(int priority, String line); 53 protected abstract void flushWriter(); 54 protected abstract void closeWriter(); 55 56 public void stopWriting() { 57 _write = false; 35 58 } 36 59 37 60 /** 38 * File may not exist or have old logs in it if not opened yet 61 * @param interval ms 62 * @since 0.9.18 39 63 */ 40 public synchronized String currentFile() { 41 if (_currentFile != null) 42 return _currentFile.getAbsolutePath(); 43 String rv = getNextFile().getAbsolutePath(); 44 // so it doesn't increment every time we call this 45 _rotationNum = -1; 46 return rv; 64 public void setFlushInterval(long interval) { 65 _flushInterval = Math.min(MAX_FLUSH_INTERVAL, Math.max(MIN_FLUSH_INTERVAL, interval)); 47 66 } 48 67 49 protected void writeRecord(LogRecord rec, String formatted) { 50 writeRecord(rec.getPriority(), formatted); 68 public void run() { 69 _write = true; 70 // don't bother on Android 71 final boolean shouldReadConfig = !SystemVersion.isAndroid(); 72 try { 73 while (_write) { 74 flushRecords(); 75 if (_write && shouldReadConfig) 76 rereadConfig(); 77 } 78 } catch (RuntimeException e) { 79 System.err.println("Error writing the log: " + e); 80 e.printStackTrace(); 81 } 82 closeWriter(); 51 83 } 52 84 53 protected synchronized void writeRecord(int priority, String val) { 54 if (val == null) return; 55 if (_currentOut == null) { 56 rotateFile(); 57 if (_currentOut == null) 58 return; // hosed 59 } 85 public void flushRecords() { flushRecords(true); } 60 86 87 public void flushRecords(boolean shouldWait) { 61 88 try { 62 _currentOut.write(val); 63 // may be a little off if a lot of multi-byte chars, but unlikely 64 _numBytesInCurrentFile += val.length(); 89 // zero copy, drain the manager queue directly 90 Queue<LogRecord> records = _manager.getQueue(); 91 if (records == null) return; 92 if (!records.isEmpty()) { 93 if (_last != null && _last.getDate() < _manager.getContext().clock().now() - 30*60*1000) 94 _last = null; 95 LogRecord rec; 96 int dupCount = 0; 97 while ((rec = records.poll()) != null) { 98 if (_manager.shouldDropDuplicates() && rec.equals(_last)) { 99 dupCount++; 100 } else { 101 if (dupCount > 0) { 102 writeDupMessage(dupCount, _last); 103 dupCount = 0; 104 } 105 writeRecord(rec); 106 } 107 _last = rec; 108 } 109 if (dupCount > 0) { 110 writeDupMessage(dupCount, _last); 111 } 112 flushWriter(); 113 } 65 114 } catch (Throwable t) { 66 if (!_write) 67 return; 68 if (++_diskFullMessageCount < MAX_DISKFULL_MESSAGES) 69 System.err.println("Error writing log, disk full? " + t); 70 //t.printStackTrace(); 71 } 72 if (_numBytesInCurrentFile >= _manager.getFileSize()) { 73 rotateFile(); 115 t.printStackTrace(); 116 } finally { 117 if (shouldWait) { 118 try { 119 synchronized (this) { 120 this.wait(_flushInterval); 121 } 122 } catch (InterruptedException ie) { // nop 123 } 124 } 74 125 } 75 126 } 76 127 77 128 /** 78 * @since 0.9.19 129 * Write a msg with the date stamp of the last duplicate 130 * @since 0.9.21 79 131 */ 80 protected void flushWriter() { 81 try { 82 if (_currentOut != null) 83 _currentOut.flush(); 84 } catch (IOException ioe) { 85 if (_write && ++_diskFullMessageCount < MAX_DISKFULL_MESSAGES) 86 System.err.println("Error writing the router log - disk full? " + ioe); 132 private void writeDupMessage(int dupCount, LogRecord lastRecord) { 133 String dmsg = dupMessage(dupCount, lastRecord, false); 134 writeRecord(lastRecord.getPriority(), dmsg); 135 if (_manager.getDisplayOnScreenLevel() <= lastRecord.getPriority() && _manager.displayOnScreen()) 136 System.out.print(dmsg); 137 dmsg = dupMessage(dupCount, lastRecord, true); 138 _manager.getBuffer().add(dmsg); 139 if (lastRecord.getPriority() >= Log.CRIT) 140 _manager.getBuffer().addCritical(dmsg); 141 } 142 143 /** 144 * Return a msg with the date stamp of the last duplicate 145 * @since 0.9.3 146 */ 147 private String dupMessage(int dupCount, LogRecord lastRecord, boolean reverse) { 148 String arrows = reverse ? (SystemVersion.isAndroid() ? "vvv" : "↓↓↓") : "^^^"; 149 return LogRecordFormatter.getWhen(_manager, lastRecord) + ' ' + arrows + ' ' + 150 _t(dupCount, "1 similar message omitted", "{0} similar messages omitted") + ' ' + arrows + 151 LogRecordFormatter.NL; 152 } 153 154 private static final String BUNDLE_NAME = "net.i2p.router.web.messages"; 155 156 /** 157 * gettext 158 * @since 0.9.3 159 */ 160 private String _t(int a, String b, String c) { 161 return Translate.getString(a, b, c, _manager.getContext(), BUNDLE_NAME); 162 } 163 164 private void rereadConfig() { 165 long now = Clock.getInstance().now(); 166 if (now - _lastReadConfig > CONFIG_READ_INTERVAL) { 167 _manager.rereadConfig(); 168 _lastReadConfig = now; 87 169 } 88 170 } 89 171 90 /** 91 * @since 0.9.19 renamed from closeFile() 92 */ 93 protected void closeWriter() { 94 Writer out = _currentOut; 95 if (out != null) { 96 try { 97 out.close(); 98 } catch (IOException ioe) {} 172 private void writeRecord(LogRecord rec) { 173 String val = LogRecordFormatter.formatRecord(_manager, rec, true); 174 writeRecord(rec, val); 175 176 // we always add to the console buffer, but only sometimes write to stdout 177 _manager.getBuffer().add(val); 178 if (rec.getPriority() >= Log.CRIT) 179 _manager.getBuffer().addCritical(val); 180 if (_manager.getDisplayOnScreenLevel() <= rec.getPriority()) { 181 if (_manager.displayOnScreen()) { 182 // wrapper and android logs already do time stamps, so reformat without the date 183 if (_manager.getContext().hasWrapper() || SystemVersion.isAndroid()) 184 System.out.print(LogRecordFormatter.formatRecord(_manager, rec, false)); 185 else 186 System.out.print(val); 187 } 99 188 } 100 189 } 101 102 /**103 * Rotate to the next file (or the first file if this is the first call)104 *105 * Caller must synch106 */107 private void rotateFile() {108 File f = getNextFile();109 _currentFile = f;110 _numBytesInCurrentFile = 0;111 File parent = f.getParentFile();112 if (parent != null) {113 if (!parent.exists()) {114 File sd = new SecureDirectory(parent.getAbsolutePath());115 boolean ok = sd.mkdirs();116 if (!ok) {117 System.err.println("Unable to create the parent directory: " + parent.getAbsolutePath());118 //System.exit(0);119 }120 }121 if (!parent.isDirectory()) {122 System.err.println("Cannot put the logs in a subdirectory of a plain file: " + f.getAbsolutePath());123 //System.exit(0);124 }125 }126 closeWriter();127 try {128 _currentOut = new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(f), "UTF-8"));129 } catch (IOException ioe) {130 if (++_diskFullMessageCount < MAX_DISKFULL_MESSAGES)131 System.err.println("Error creating log file [" + f.getAbsolutePath() + "]" + ioe);132 }133 }134 135 /**136 * Get the next file in the rotation137 *138 * Caller must synch139 */140 private File getNextFile() {141 String pattern = _manager.getBaseLogfilename();142 File f = new File(pattern);143 File base = null;144 if (!f.isAbsolute())145 base = _manager.getContext().getLogDir();146 147 if ( (pattern.indexOf('#') < 0) && (pattern.indexOf('@') <= 0) ) {148 if (base != null)149 return new File(base, pattern);150 else151 return f;152 }153 154 int max = _manager.getRotationLimit();155 if (_rotationNum == -1) {156 return getFirstFile(base, pattern, max);157 }158 159 // we're in rotation, just go to the next160 _rotationNum++;161 if (_rotationNum > max) _rotationNum = 0;162 163 String newf = replace(pattern, _rotationNum);164 if (base != null)165 return new File(base, newf);166 return new File(newf);167 }168 169 /**170 * Retrieve the first file, updating the rotation number accordingly171 *172 * Caller must synch173 */174 private File getFirstFile(File base, String pattern, int max) {175 for (int i = 0; i < max; i++) {176 File f;177 if (base != null)178 f = new File(base, replace(pattern, i));179 else180 f = new File(replace(pattern, i));181 if (!f.exists()) {182 _rotationNum = i;183 return f;184 }185 }186 187 // all exist, pick the oldest to replace188 File oldest = null;189 for (int i = 0; i < max; i++) {190 File f;191 if (base != null)192 f = new File(base, replace(pattern, i));193 else194 f = new File(replace(pattern, i));195 if (oldest == null) {196 oldest = f;197 } else {198 if (f.lastModified() < oldest.lastModified()) {199 _rotationNum = i;200 oldest = f;201 }202 }203 }204 return oldest;205 }206 207 private static final String replace(String pattern, int num) {208 char c[] = pattern.toCharArray();209 StringBuilder buf = new StringBuilder();210 for (int i = 0; i < c.length; i++) {211 if ( (c[i] != '#') && (c[i] != '@') )212 buf.append(c[i]);213 else214 buf.append(num);215 }216 return buf.toString();217 }218 190 }
Note: See TracChangeset
for help on using the changeset viewer.