Get all logging output with mock
logging, mocking, python, unit-testing
Solution
stdlib
Since Python 3.4 the batteries' `unittest` has `assertLogs`. When used without `logger` and `level` arguments, it catches all logging (suppresses existing handlers). You can later access recorded entries from the context manager's `records` attribute. Text output strings are stored in `output` list.
import logging
import unittest
class TestLogging(unittest.TestCase):
def test(self):
with self.assertLogs() as ctx:
logging.getLogger('foo').info('message from foo')
logging.getLogger('bar').info('message from bar')
print(ctx.records)
Tornado
For Python 2 I usually take Tornado's `ExpectLog`. It's self-contained and works for normal Python code. It's actually more elegant solution then stdlib's, because instead of several class, `ExpectLog` is just a normal `logging.Filter` (a class, source). But it lacks a couple of features, including access to recorded entries, so usually I also extend it a bit, like:
class ExpectLog(logging.Filter):
def __init__(self, logger, regex, required=True, level=None):
if isinstance(logger, basestring):
logger = logging.getLogger(logger)
self.logger = logger
self.orig_level = self.logger.level
self.level = level
self.regex = re.compile(regex)
self.formatter = logging.Formatter()
self.required = required
self.matched = []
self.logged_stack = False
def filter(self, record):
if record.exc_info:
self.logged_stack = True
message = self.formatter.format(record)
if self.regex.search(message):
self.matched.append(record)
return False
return True
def __enter__(self):
self.logger.addFilter(self)
if self.level:
self.logger.setLevel(self.level)
return self
def __exit__(self, typ, value, tb):
self.logger.removeFilter(self)
if self.level:
self.logger.setLevel(self.orig_level)
if not typ and self.required and not self.matched:
raise Exception("did not get expected log message")
Then you can have something like:
class TestLogging(unittest.TestCase):
def testTornadoself):
logging.basicConfig(level = logging.INFO)
with ExpectLog('foo', '.*', required = False) as ctxFoo:
with ExpectLog('bar', '.*', required = False) as ctxBar:
logging.getLogger('foo').info('message from foo')
logging.getLogger('bar').info('message from bar')
print(ctxFoo.matched)
print(ctxBar.matched)
However, note that for the filter approach current logging level is important (can be overridden with `level` argument), and also you need a filter per logger of interest. You can follow the approach and make something that fits your case better.
Update
Alternatively there's unittest2 backport for Python 2 which has `assertLogs`.
Problem
I want to get all logging output with mock. I searched, but only found ways to mock explicitly logging.info or logging.warn. I need all output, whatever logging level was set. ``` def test_foo(): def my_log(...): logs.append(...) with mock.patch('logging.???', my_log): ... ``` In our libraries we use this: ``` import logging logger=logging.getLogger(__name__) def foo(): logger.info(...) ```