diff --git a/core/java/src/net/i2p/util/LogRecord.java b/core/java/src/net/i2p/util/LogRecord.java
index d9a4f6441a9cb42dc9bdbb3a7c84212fbfe42cd6..b1bd6b8d4483f5863530dbf73f4dcdb60b4db9a6 100644
--- a/core/java/src/net/i2p/util/LogRecord.java
+++ b/core/java/src/net/i2p/util/LogRecord.java
@@ -1,5 +1,7 @@
 package net.i2p.util;
 
+import net.i2p.data.DataHelper;
+
 /*
  * free (adj.): unencumbered; not under the control of others
  * Written by jrandom in 2003 and released into the public domain 
@@ -59,4 +61,19 @@ class LogRecord {
     public Throwable getThrowable() {
         return _throwable;
     }
+
+    /**
+     *  Matches source class, message string, and throwable class only.
+     *  @since 0.9.3
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof LogRecord))
+            return false;
+        LogRecord r = (LogRecord) o;
+        return _source == r._source &&
+               DataHelper.eq(_message, r._message) &&
+               ((_throwable == null && r._throwable == null) ||
+                (_throwable != null && r._throwable != null && _throwable.getClass() == r._throwable.getClass()));
+    }
 }
diff --git a/core/java/src/net/i2p/util/LogWriter.java b/core/java/src/net/i2p/util/LogWriter.java
index 5cde9ce33472d5fcce1f146bbcb3d2e5e068837e..a26433bb8afc4d22b63e1627ff760f15ed144ee8 100644
--- a/core/java/src/net/i2p/util/LogWriter.java
+++ b/core/java/src/net/i2p/util/LogWriter.java
@@ -26,8 +26,8 @@ class LogWriter 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;
     private final static long FLUSH_INTERVAL = 9 * 1000;
-    private long _lastReadConfig = 0;
-    private long _numBytesInCurrentFile = 0;
+    private long _lastReadConfig;
+    private long _numBytesInCurrentFile;
     // volatile as it changes on log file rotation
     private volatile Writer _currentOut;
     private int _rotationNum = -1;
@@ -66,16 +66,35 @@ class LogWriter implements Runnable {
     }
 
     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()) {
+                LogRecord last = null;
                 LogRecord rec;
+                int dupCount = 0;
                 while ((rec = records.poll()) != null) {
-                    writeRecord(rec);
+                    if (rec.equals(last)) {
+                        dupCount++;
+                    } else {
+                        if (dupCount > 0) {
+                            if (dupCount == 1)
+                                writeRecord("*** 1 similar message omitted\n");
+                            else
+                                writeRecord("*** " + dupCount + " similar messages omitted\n");
+                            dupCount = 0;
+                        }
+                        last = rec;
+                        writeRecord(rec);
+                    }
                 }
+                if (dupCount == 1)
+                    writeRecord("*** 1 similar message omitted\n");
+                else if (dupCount > 0)
+                    writeRecord("*** " + dupCount + " similar messages omitted\n");
                 try {
                     if (_currentOut != null)
                         _currentOut.flush();