diff --git a/apps/routerconsole/java/src/net/i2p/router/web/EventLogHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/EventLogHelper.java new file mode 100644 index 000000000..1b9e86fa2 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/EventLogHelper.java @@ -0,0 +1,217 @@ +package net.i2p.router.web; + +import java.io.IOException; +import java.io.Writer; +import java.text.Collator; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; +import java.util.TreeMap; + +import net.i2p.data.DataHelper; +import net.i2p.router.util.EventLog; + +/** + * /events.jsp + */ +public class EventLogHelper extends FormHandler { + protected Writer _out; + private long _from, _age; + //private long _to = Long.MAX_VALUE; + private String _event = ALL; + // EventLog name to translated display string + private final Map _xevents; + + private static final String ALL = "all"; + private static final String[] _events = new String[] { + EventLog.ABORTED, _x("Aborted startup"), + EventLog.CHANGE_IP, _x("Changed IP"), + EventLog.CHANGE_PORT, _x("Changed port"), + EventLog.CLOCK_SHIFT, _x("Clock shifted"), + EventLog.CRASHED, _x("Crashed"), + EventLog.CRITICAL, _x("Critical error"), + EventLog.INSTALLED, _x("Installed new version"), + EventLog.INSTALL_FAILED, _x("Install failed"), + EventLog.NETWORK, _x("Network error"), + EventLog.NEW_IDENT, _x("New router identity"), + EventLog.OOM, _x("Out of memory error"), + EventLog.REKEYED, _x("New router identity"), + EventLog.RESEED, _x("Reseeded router"), + EventLog.SOFT_RESTART, _x("Soft restart"), + EventLog.STARTED, _x("Started router"), + EventLog.STOPPED, _x("Stopped router"), + EventLog.UPDATED, _x("Updated router"), + EventLog.WATCHDOG, _x("Watchdog warning") + }; + private static final long DAY = 24*60*60*1000L; + private static final long[] _times = { 0, DAY, 7*DAY, 30*DAY, 365*DAY }; + + public EventLogHelper() { + super(); + _xevents = new HashMap(1 + (_events.length / 2)); + } + + /** set the defaults after we have a context */ + @Override + public void setContextId(String contextId) { + super.setContextId(contextId); + for (int i = 0; i < _events.length; i += 2) { + _xevents.put(_events[i], _(_events[i + 1])); + } + } + + public void storeWriter(Writer out) { _out = out; } + + public void setFrom(String s) { + try { + _age = Long.parseLong(s); + if (_age > 0) + _from = _context.clock().now() - _age; + else + _from = 0; + } catch (NumberFormatException nfe) { + _age = 0; + _from = 0; + } + } + + //public void setTo(String s) { + // _to = s; + //} + + public void setType(String s) { + _event = s; + } + + public String getForm() { + // too hard to use the standard formhandler.jsi / FormHandler.java session nonces + // since graphs.jsp needs the refresh value in its . + // So just use the "shared/console nonce". + String nonce = CSSHelper.getNonce(); + try { + _out.write("

" + _("Display Events") + "

"); + _out.write("
\n" + + "\n" + + "\n"); + _out.write(_("Events since") + ":
"); + _out.write(_("Event type") + ": " + + "
"); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + return ""; + } + + private void writeOption(String key, String val) throws IOException { + _out.write("\n"); + } + + private void writeOption(long age) throws IOException { + _out.write("\n"); + } + + public String getEvents() { + EventLog ev = _context.router().eventLog(); + // oldest first + Map events; + boolean isAll = ALL.equals(_event); + if (isAll) + events = ev.getEvents(_from); + else + events = ev.getEvents(_event, _from); + String xev = _xevents.get(_event); + if (xev == null) + xev = _event; + if (events.isEmpty()) { + if (isAll) { + if (_age == 0) + return _("No events found"); + return _("No events found in previous {0}", DataHelper.formatDuration2(_age)); + } + if (_age == 0) + return _("No \"{0}\" events found", xev); + return _("No \"{0}\" events found in previous {1}", xev, DataHelper.formatDuration2(_age)); + } + StringBuilder buf = new StringBuilder(2048); + buf.append(""); + + SimpleDateFormat fmt = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM); + // the router sets the JVM time zone to UTC but saves the original here so we can get it + String systemTimeZone = _context.getProperty("i2p.systemTimeZone"); + if (systemTimeZone != null) + fmt.setTimeZone(TimeZone.getTimeZone(systemTimeZone)); + + List> entries = new ArrayList>(events.entrySet()); + Collections.reverse(entries); + for (Map.Entry e : entries) { + long time = e.getKey().longValue(); + String event = e.getValue(); + buf.append(""); + } + buf.append("
"); + buf.append(_("Time")); + buf.append(""); + if (isAll) { + buf.append(_("Event")); + buf.append(""); + buf.append(_("Details")); + } else { + buf.append(xev); + } + buf.append("
"); + buf.append(fmt.format(new Date(time))); + buf.append(""); + if (isAll) { + String[] s = event.split(" ", 2); + String xs = _xevents.get(s[0]); + if (xs == null) + xs = s[0]; + buf.append(xs); + buf.append(""); + if (s.length > 1) + buf.append(s[1]); + } else { + buf.append(event); + } + buf.append("
"); + return buf.toString(); + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java index 99064c7cc..b4d147bcc 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java @@ -267,4 +267,14 @@ public abstract class FormHandler { public String _(String s, Object o, Object o2) { return Messages.getString(s, o, o2, _context); } + + /** + * Mark a string for extraction by xgettext and translation. + * Use this only in static initializers. + * It does not translate! + * @return s + */ + public static String _x(String s) { + return s; + } } diff --git a/apps/routerconsole/jsp/events.jsp b/apps/routerconsole/jsp/events.jsp new file mode 100644 index 000000000..b67aa5dd5 --- /dev/null +++ b/apps/routerconsole/jsp/events.jsp @@ -0,0 +1,28 @@ +<%@page contentType="text/html"%> +<%@page trimDirectiveWhitespaces="true"%> +<%@page pageEncoding="UTF-8"%> + + + +<%@include file="css.jsi" %> +<%=intl.title("events")%> + + " /> +<% /* GraphHelper sets the defaults in setContextId, so setting the properties must be after the context */ %> + +<% + eventHelper.storeWriter(out); + eventHelper.storeMethod(request.getMethod()); +%> + +<%@include file="summaryajax.jsi" %> + +<%@include file="summary.jsi" %> +

<%=intl._("I2P Event Log")%>

+
+
+
+ + + +
diff --git a/apps/routerconsole/jsp/logs.jsp b/apps/routerconsole/jsp/logs.jsp index 538ae2abf..a028d9334 100644 --- a/apps/routerconsole/jsp/logs.jsp +++ b/apps/routerconsole/jsp/logs.jsp @@ -37,10 +37,10 @@

<%=intl._("Note that system information, log timestamps, and log messages may provide clues to your location; please review everything you include in a bug report.")%>

<%=intl._("Critical Logs")%>

-

<%=intl._("Router Logs")%> (<%=intl._("configure")%>)

-
+

<%=intl._("Event Logs")%>

+ <%=intl._("View event logs")%>

<%=intl._("Service (Wrapper) Logs")%>

-
+ diff --git a/history.txt b/history.txt index e1d573525..5fcdc5897 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,12 @@ +2014-07-03 zzz + * Base64: + - Catch numerous decoding errors that were previously misdecoded (ticket #1318) + - Improve decoding efficiency, reduce copies + - encode(String) now uses UTF-8 encoding + - decode() now accepts short strings without trailing '=' + - whitespace in decode will now cause an error, was previously ignored + * Console: Add event log viewer (ticket #1117) + 2014-07-02 kytv * Update Java Service Wrapper to v3.5.25 - Windows: x86 and x64 versions self-compiled with VS2010 in diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index e36cd3b14..7f4edb975 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 12; + public final static long BUILD = 13; /** for example "-test" */ public final static String EXTRA = ""; diff --git a/router/java/src/net/i2p/router/util/EventLog.java b/router/java/src/net/i2p/router/util/EventLog.java index 1e6f166f8..086fbbdc5 100644 --- a/router/java/src/net/i2p/router/util/EventLog.java +++ b/router/java/src/net/i2p/router/util/EventLog.java @@ -144,4 +144,43 @@ public class EventLog { } 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 getEvents(long since) { + SortedMap rv = new TreeMap(); + 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 = line.split(" ", 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; + } }