Permalink
Browse files

Add a DedupHandler

  • Loading branch information...
1 parent 43a6358 commit 0a4556029eec698b90ff1f8902a9a4e5f6d9a900 @alonho alonho committed Oct 16, 2013
Showing with 64 additions and 1 deletion.
  1. +3 −0 docs/api/more.rst
  2. +50 −1 logbook/more.py
  3. +11 −0 tests/test_logbook.py
View
3 docs/api/more.rst
@@ -30,6 +30,9 @@ Special Handlers
.. autoclass:: ExceptionHandler
:members:
+.. autoclass:: DedupHandler
+ :members:
+
Colorized Handlers
------------------
View
51 logbook/more.py
@@ -10,9 +10,10 @@
"""
import re
import os
+from collections import defaultdict
from cgi import parse_qsl
-from logbook.base import RecordDispatcher, NOTSET, ERROR, NOTICE
+from logbook.base import RecordDispatcher, dispatch_record, NOTSET, ERROR, NOTICE
from logbook.handlers import Handler, StringFormatter, \
StringFormatterHandlerMixin, StderrHandler
from logbook._termcolors import colorize
@@ -357,3 +358,51 @@ def handle(self, record):
if self.should_handle(record):
raise self.exc_type(self.format(record))
return False
+
+class DedupHandler(Handler):
+ """A handler that deduplicates log messages.
+
+ It emits each unique log record once, along with the number of times it was emitted.
+ Example:::
+
+ with logbook.more.DedupHandler():
+ logbook.error('foo')
+ logbook.error('bar')
+ logbook.error('foo')
+
+ The expected output:::
+
+ message repeated 2 times: foo
+ message repeated 1 times: bar
+ """
+ def __init__(self, format_string='message repeated {count} times: {message}', *args, **kwargs):
+ Handler.__init__(self, bubble=False, *args, **kwargs)
+ self._format_string = format_string
+ self.clear()
+
+ def clear(self):
+ self._message_to_count = defaultdict(int)
+ self._unique_ordered_records = []
+
+ def pop_application(self):
+ Handler.pop_application(self)
+ self.flush()
+
+ def pop_thread(self):
+ Handler.pop_thread(self)
+ self.flush()
+
+ def handle(self, record):
+ if not record.message in self._message_to_count:
+ self._unique_ordered_records.append(record)
+ self._message_to_count[record.message] += 1
+ return True
+
+ def flush(self):
+ for record in self._unique_ordered_records:
+ record.message = self._format_string.format(message=record.message, count=self._message_to_count[record.message])
+ # record.dispatcher is the logger who created the message, it's sometimes supressed (by logbook.info for example)
+ dispatch = record.dispatcher.call_handlers if record.dispatcher is not None else dispatch_record
+ dispatch(record)
+ self.clear()
+
View
11 tests/test_logbook.py
@@ -1097,6 +1097,17 @@ def test_exception_handler_specific_level(self):
self.assertIn('WARNING: testlogger: here i am', caught.exception.args[0])
self.assertIn('this is irrelevant', test_handler.records[0].message)
+ def test_dedup_handler(self):
+ from logbook.more import DedupHandler
+ with logbook.TestHandler() as test_handler:
+ with DedupHandler():
+ self.log.info('foo')
+ self.log.info('bar')
+ self.log.info('foo')
+ self.assertEqual(2, len(test_handler.records))
+ self.assertIn('message repeated 2 times: foo', test_handler.records[0].message)
+ self.assertIn('message repeated 1 times: bar', test_handler.records[1].message)
+
class QueuesTestCase(LogbookTestCase):
def _get_zeromq(self):
from logbook.queues import ZeroMQHandler, ZeroMQSubscriber

0 comments on commit 0a45560

Please sign in to comment.