diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java
index 51f04d48c348ebc8cb1e09606c30045ad580cff8..83d354008b56dd8a1fa6101df886f54c63735a68 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java
@@ -28,15 +28,28 @@ public class ConfigServiceHandler extends FormHandler {
         } else if ("Cancel graceful shutdown".equals(_action)) {
             _context.router().cancelGracefulShutdown();
             addFormNotice("Graceful shutdown cancelled");
+        } else if ("Hard restart".equals(_action)) {
+            _context.router().shutdown(Router.EXIT_HARD_RESTART);
+            addFormNotice("Hard restart requested");
         } else if ("Dump threads".equals(_action)) {
             WrapperManager.requestThreadDump();
             addFormNotice("Threads dumped to logs/wrapper.log");
         } else if ("Show systray icon".equals(_action)) {
-            SysTray.getInstance().show();
-            addFormNotice("Systray icon enabled (if possible)");
+            SysTray tray = SysTray.getInstance();
+            if (tray != null) {
+                tray.show();
+                addFormNotice("Systray enabled");
+            } else {
+                addFormNotice("Systray not supported on this platform");
+            }
         } else if ("Hide systray icon".equals(_action)) {
-            SysTray.getInstance().hide();
-            addFormNotice("Systray icon disabled");
+            SysTray tray = SysTray.getInstance();
+            if (tray != null) {
+                tray.hide();
+                addFormNotice("Systray disabled");
+            } else {
+                addFormNotice("Systray not supported on this platform");
+            }
         } else {
             addFormNotice("Blah blah blah.  whatever.  I'm not going to " + _action);
         }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java
index ab069ec769bdf4387700504ebce0401ee4302564..ee8299bfb7bd59e2a617e19b803ac094780b7a65 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java
@@ -4,6 +4,7 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.List;
 
+import net.i2p.data.DataHelper;
 import net.i2p.router.RouterContext;
 
 public class LogsHelper {
@@ -39,4 +40,12 @@ public class LogsHelper {
         
         return buf.toString();
     }
+    
+    public String getServiceLogs() {
+        String str = DataHelper.readTextFile("logs/wrapper.log", 500);
+        if (str == null) 
+            return "";
+        else
+            return "<pre>" + str + "</pre>";
+    }
 }
diff --git a/apps/routerconsole/jsp/configservice.jsp b/apps/routerconsole/jsp/configservice.jsp
index 7dd44a452a494f7ffae1c9a97b4edc52457eaa5a..6728c1862b4383f9ebb7a1ea6ed304cc3755dda9 100644
--- a/apps/routerconsole/jsp/configservice.jsp
+++ b/apps/routerconsole/jsp/configservice.jsp
@@ -31,6 +31,7 @@
  <input type="submit" name="action" value="Shutdown gracefully" />
  <input type="submit" name="action" value="Shutdown immediately" />
  <input type="submit" name="action" value="Cancel graceful shutdown" />
+ <input type="submit" name="action" value="Hard restart" />
  <h4>Systray integration</h4>
  On the windows platform, there is a small application to sit in the system 
  tray, allowing you to view the router's status (later on, I2P client applications
@@ -50,7 +51,8 @@
  <input type="submit" name="action" value="Don't run I2P on startup" />
  <h4>Debugging</h4>
  At times, it may be helpful to debug I2P by getting a thread dump.  To do so, 
- please select the following option and review the thread dumped to logs/wrapper.log.<br />
+ please select the following option and review the thread dumped to 
+<a href="logs.jsp#servicelogs">logs/wrapper.log</a>.<br />
  <input type="submit" name="action" value="Dump threads" />
  </form>
 </div>
diff --git a/apps/routerconsole/jsp/logs.jsp b/apps/routerconsole/jsp/logs.jsp
index f46f2c7d63690bdc101cbe6e1c6aac668b9d5d16..10b61c1155ba8362db0f0ea7f88d73374c4d19e9 100644
--- a/apps/routerconsole/jsp/logs.jsp
+++ b/apps/routerconsole/jsp/logs.jsp
@@ -13,7 +13,11 @@
 <div class="main" id="main">
  <jsp:useBean class="net.i2p.router.web.LogsHelper" id="logsHelper" scope="request" />
  <jsp:setProperty name="logsHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
+ <h4>Router logs:</h4>
  <jsp:getProperty name="logsHelper" property="logs" />
+ <hr />
+ <h4>Service logs:</h4><a name="servicelogs"> </a>
+ <jsp:getProperty name="logsHelper" property="serviceLogs" />
 </div>
 
 </body>
diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java
index 738d84bc0af92b0f913007f441c1125ad8cc2fc9..0c4f6e3bd8433168b6505b9ec924350c19ba532e 100644
--- a/core/java/src/net/i2p/data/DataHelper.java
+++ b/core/java/src/net/i2p/data/DataHelper.java
@@ -606,4 +606,34 @@ public class DataHelper {
         //               * (((double) rv.length) / ((double) orig.length)) + "% savings)");
         return rv;
     }
+    
+    /**
+     * Read in the last few lines of a (newline delimited) textfile, or null if
+     * the file doesn't exist.
+     *
+     */
+    public static String readTextFile(String filename, int maxNumLines) {
+        File f = new File(filename);
+        if (!f.exists()) return null;
+        FileInputStream fis = null;
+        try {
+            fis = new FileInputStream(f);
+            BufferedReader in = new BufferedReader(new InputStreamReader(fis));
+            List lines = new ArrayList(maxNumLines);
+            String line = null;
+            while ( (line = in.readLine()) != null) {
+                lines.add(line);
+                while (lines.size() > maxNumLines)
+                    lines.remove(0);
+            }
+            StringBuffer buf = new StringBuffer(lines.size() * 80);
+            for (int i = 0; i < lines.size(); i++)
+                buf.append((String)lines.get(i)).append('\n');
+            return buf.toString();
+        } catch (IOException ioe) {
+            return null;
+        } finally {
+            if (fis != null) try { fis.close(); } catch (IOException ioe) {}
+        }
+    }
 }
diff --git a/core/java/src/net/i2p/util/I2PThread.java b/core/java/src/net/i2p/util/I2PThread.java
index 88d89da74ae5e15c1cdd2d2e207ca1f1e8acb3c1..b4bd8d740ce09e40272485c99d48d4e468d6847d 100644
--- a/core/java/src/net/i2p/util/I2PThread.java
+++ b/core/java/src/net/i2p/util/I2PThread.java
@@ -20,33 +20,51 @@ import java.util.Set;
  *
  */
 public class I2PThread extends Thread {
-    private static Log _log;
+    private static volatile Log _log;
     private static Set _listeners = new HashSet(4);
+    private String _name;
+    private Exception _createdBy;
 
     public I2PThread() {
         super();
+        if ( (_log == null) || (_log.shouldLog(Log.DEBUG)) )
+            _createdBy = new Exception("Created by");
     }
 
     public I2PThread(String name) {
         super(name);
+        if ( (_log == null) || (_log.shouldLog(Log.DEBUG)) )
+            _createdBy = new Exception("Created by");
     }
 
     public I2PThread(Runnable r) {
         super(r);
+        if ( (_log == null) || (_log.shouldLog(Log.DEBUG)) )
+            _createdBy = new Exception("Created by");
     }
 
     public I2PThread(Runnable r, String name) {
         super(r, name);
+        if ( (_log == null) || (_log.shouldLog(Log.DEBUG)) )
+            _createdBy = new Exception("Created by");
+    }
+    
+    private void log(int level, String msg) { log(level, msg, null); }
+    private void log(int level, String msg, Throwable t) {
+        // we cant assume log is created
+        if (_log == null) _log = new Log(I2PThread.class);
+        if (_log.shouldLog(level))
+            _log.log(level, msg, t);
     }
 
     public void run() {
+        _name = Thread.currentThread().getName();
+        log(Log.DEBUG, "New thread started: " + _name, _createdBy);
         try {
             super.run();
         } catch (Throwable t) {
             try {
-                // we cant assume log is created
-                if (_log == null) _log = new Log(I2PThread.class);
-                _log.log(Log.CRIT, "Killing thread " + getName(), t);
+                log(Log.CRIT, "Killing thread " + getName(), t);
             } catch (Throwable woof) {
                 System.err.println("Died within the OOM itself");
                 t.printStackTrace();
@@ -54,6 +72,12 @@ public class I2PThread extends Thread {
             if (t instanceof OutOfMemoryError)
                 fireOOM((OutOfMemoryError)t);
         }
+        log(Log.DEBUG, "Thread finished gracefully: " + _name);
+    }
+    
+    protected void finalize() throws Throwable {
+        log(Log.DEBUG, "Thread finalized: " + _name);
+        super.finalize();
     }
     
     private void fireOOM(OutOfMemoryError oom) {
diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java
index 2459b715e8e3660cf8184e32aa02734d099aa738..bf267b657f5c77cc305e642e70b366a5424555bb 100644
--- a/router/java/src/net/i2p/router/Router.java
+++ b/router/java/src/net/i2p/router/Router.java
@@ -539,6 +539,7 @@ public class Router {
     public static final int EXIT_GRACEFUL = 2;
     public static final int EXIT_HARD = 3;
     public static final int EXIT_OOM = 10;
+    public static final int EXIT_HARD_RESTART = 4;
     
     public void shutdown(int exitCode) {
         _isAlive = false;