package net.i2p.router.util; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.io.IOException; import java.io.OutputStream; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.util.SecureFileOutputStream; import net.i2p.util.SystemVersion; /** * Simple event logger for occasional events, * with caching for reads. * Does not keep the file open. * @since 0.9.3 */ public class EventLog { private final I2PAppContext _context; private final File _file; /** event to cached map */ private final Map<String, SortedMap<Long, String>> _cache; /** event to starting time of cached map */ private final Map<String, Long> _cacheTime; /** for convenience, not required */ public static final String ABORTED = "aborted"; public static final String BECAME_FLOODFILL = "becameFloodfill"; public static final String CHANGE_IP = "changeIP"; public static final String CHANGE_PORT = "changePort"; public static final String CLOCK_SHIFT = "clockShift"; public static final String CRASHED = "crashed"; public static final String CRITICAL = "critical"; public static final String INSTALLED = "installed"; public static final String INSTALL_FAILED = "installFailed"; public static final String NETWORK = "network"; public static final String NEW_IDENT = "newIdent"; public static final String NOT_FLOODFILL = "disabledFloodfill"; public static final String OOM = "oom"; public static final String REACHABILITY = "reachability"; public static final String REKEYED = "rekeyed"; public static final String RESEED = "reseed"; public static final String SOFT_RESTART = "softRestart"; public static final String STARTED = "started"; public static final String STOPPED = "stopped"; public static final String UPDATED = "updated"; public static final String WATCHDOG = "watchdog"; /** * @param file should be absolute */ public EventLog(I2PAppContext ctx, File file) { //if (!file.isAbsolute()) // throw new IllegalArgumentException(); _context = ctx; _file = file; _cache = new HashMap<String, SortedMap<Long, String>>(4); _cacheTime = new HashMap<String, Long>(4); } /** * Append an event. Fails silently. * @param event no spaces, e.g. "started" * @throws IllegalArgumentException if event contains a space or newline */ public void addEvent(String event) { addEvent(event, null); } /** * Append an event. Fails silently. * @param event no spaces or newlines, e.g. "started" * @param info no newlines, may be blank or null * @throws IllegalArgumentException if event contains a space or either contains a newline */ public synchronized void addEvent(String event, String info) { if (event.contains(" ") || event.contains("\n") || (info != null && info.contains("\n"))) throw new IllegalArgumentException(); _cache.remove(event); _cacheTime.remove(event); OutputStream out = null; try { out = new SecureFileOutputStream(_file, true); StringBuilder buf = new StringBuilder(128); buf.append(_context.clock().now()).append(' ').append(event); if (info != null && info.length() > 0) buf.append(' ').append(info); if (SystemVersion.isWindows()) buf.append('\r'); buf.append('\n'); out.write(buf.toString().getBytes("UTF-8")); } catch (IOException ioe) { } finally { if (out != null) try { out.close(); } catch (IOException ioe) {} } } /** * Caches. * Fails silently. * @param event matching this event only, case sensitive * @param since since this time, 0 for all * @return non-null, Map of times to (possibly empty) info strings, sorted, earliest first, unmodifiable */ public synchronized SortedMap<Long, String> getEvents(String event, long since) { SortedMap<Long, String> rv = _cache.get(event); if (rv != null) { Long cacheTime = _cacheTime.get(event); if (cacheTime != null) { if (since >= cacheTime.longValue()) return rv.tailMap(Long.valueOf(since)); } } rv = new TreeMap<Long, String>(); BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader( new FileInputStream(_file), "UTF-8")); String line = null; while ( (line = br.readLine()) != null) { try { String[] s = DataHelper.split(line.trim(), " ", 3); if (!s[1].equals(event)) continue; long time = Long.parseLong(s[0]); if (time <= since) continue; Long ltime = Long.valueOf(time); String info = s.length > 2 ? s[2] : ""; rv.put(ltime, info); } catch (IndexOutOfBoundsException ioobe) { } catch (NumberFormatException nfe) { } } rv = Collections.unmodifiableSortedMap(rv); _cache.put(event, rv); _cacheTime.put(event, Long.valueOf(since)); } catch (IOException ioe) { } finally { if (br != null) try { br.close(); } catch (IOException ioe) {} } return rv; } /** * All events since a given time. * Does not cache. Fails silently. * Values in the returned map have the format "event[ info]". * Events do not contain spaces. * * @param since since this time, 0 for all * @return non-null, Map of times to info strings, sorted, earliest first, unmodifiable * @since 0.9.14 */ public synchronized SortedMap<Long, String> getEvents(long since) { SortedMap<Long, String> rv = new TreeMap<Long, String>(); BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader( new FileInputStream(_file), "UTF-8")); String line = null; while ( (line = br.readLine()) != null) { try { String[] s = DataHelper.split(line.trim(), " ", 2); if (s.length < 2) continue; long time = Long.parseLong(s[0]); if (time <= since) continue; Long ltime = Long.valueOf(time); rv.put(ltime, s[1]); } catch (IndexOutOfBoundsException ioobe) { } catch (NumberFormatException nfe) { } } rv = Collections.unmodifiableSortedMap(rv); } catch (IOException ioe) { } finally { if (br != null) try { br.close(); } catch (IOException ioe) {} } return rv; } }