Logging capture¶
Within a context manager, capture log messages from third party packages
Capture logging messages from package foo, INFO level and higher, into a list.
Can optionally set formatting. Has sane default, normally leave out the logging message format string.
import logging
from logging_strict.tech_niques import captureLogs
from logging_strict.constants import LOG_FORMAT
msg0 = 'first msg'
msg1 = 'second msg'
with captureLogs('foo', level='INFO', format_=LOG_FORMAT) as cm:
logging.getLogger('foo').info(msg0)
logging.getLogger('foo.bar').error(msg1)
out = cm.output
line0 = out[0]
line1 = out[1]
assert "INFO" in line0
assert msg0 in line0
assert "ERROR" in line1
assert msg1 in line1
Can be chained with other context managers. Such as capturing the streams as well
import logging
import sys
from logging_strict.tech_niques import CaptureOutput, captureLogs
msg0 = 'first msg'
msg1 = 'second msg'
msg_err = "StdMoooooo!"
msg_out = "StdMagpie"
with (
captureLogs('foo', level='INFO') as cm,
CaptureOutput() as cow,
):
logging.getLogger('foo').info(msg0)
logging.getLogger('foo.bar').error(msg1)
sys.stdout.write(msg_out)
sys.stderr.write(msg_err)
assert cow.stderr == msg_err
assert cow.stdout == msg_out
out = cm.output
line0 = out[0]
line1 = out[1]
assert "INFO" in line0
assert msg0 in line0
assert "ERROR" in line1
assert msg1 in line1
with block using parentheses style is useful when there is more than one context manager. Even with only one context manager, with block using parentheses is the preferred style.
When when chaining context managers together, it’s a one liner
In addition to the two context managers above, in unittests,
unittest.mock.patch() can alter modules behavior
and results without changes to the modules source code.
sync and async logging¶
For synchronous logging, a context manager to fix the issue of redirecting stdout/stderr.
from logging_strict.tech_niques import LoggerRedirectorFor asynchronous logging, using package, aiologger
unittest assertLogs/assertNoLogs¶
tl;dr;¶
unittest.TestCase.assertLogs() assertion makes it
ill-suited for general use, besides within unittests
The details¶
unittest.TestCase.assertLogs() does log capturing
Tests that at least one message is logged on the logger or one of its children, with at least the given level.
So captureLogs is suitable for general usage
Having made that strong disclaimer, lets see how assertLogs works
Code snippet¶
Source unittest docs
Confirmed in unittest #12, tests/utils/test_logging_capture
class DocumentAssertLogs(unittest.TestCase):
def test_assert_logging_output(self):
with self.assertLogs('foo', level='INFO') as cm:
logging.getLogger('foo').info('first message')
logging.getLogger('foo.bar').error('second message')
self.assertEqual(cm.output, [
'INFO:foo:first message',
'ERROR:foo.bar:second message',
])
Into the rabbit hole¶
More in-depth low level implementation notes
See also
unittest._log
_CapturingHandler
_AssertLogsContext
https://github.com/python/cpython/blob/cd87737a1de9a3b766358912985ffae511c3911d/Lib/unittest/_log.py
unittest.case.TestCase
_BaseTestCaseContext
Module private variables
- logging_strict.tech_niques.logging_capture.__all__: tuple[str, str] = ("captureLogs", "captureLogsMany")¶
Exported objects from this module
Module objects
- class logging_strict.tech_niques.logging_capture._LoggingWatcher(records: MutableSequence[LogRecord] = NOTHING, output: MutableSequence[str] = NOTHING)
Replaces collections.namedtuple
- getHandlerByName(name)
Get a handler with the specified name, or None if there isn’t one with that name.
- Parameters:
- Returns:
A logging handler func
- Return type:
- getHandlerNames()
Return all known handler names as an immutable set
- class logging_strict.tech_niques.logging_capture._CapturingHandler
A logging handler capturing all (raw and formatted) logging output.
- emit(record)
Save record. Format/Save message
- Parameters:
record¶ (logging.LogRecord) – logging record. Save as record and as str message
- flush()
Flush records
- logging_strict.tech_niques.logging_capture.captureLogs(logger=None, level=None, format_='%(levelname)s %(module)s %(funcName)s: %(lineno)d: %(message)s')¶
A context manager to capture logging a loggers logging output
Example:
import logging from logging_strict.tech_niques import captureLogs with captureLogs('foo', level='INFO') as cm: logging.getLogger('foo').info('first message') logging.getLogger('foo.bar').error('second message') print(cm.output)
The watcher (
logging_strict.tech_niques.logging_capture._LoggingWatcher) has attributes:output
records
unformatted records
- Parameters:
- Returns:
Context manager yields one
logging_strict.tech_niques.logging_capture._LoggingWatcher. Which stores the log records/messages- Return type:
Iterator[logging_strict.tech_niques.logging_capture._LoggingWatcher]
See also
Context manager howto, PEP 343
- logging_strict.tech_niques.logging_capture.captureLogsMany(loggers=(), levels=(), format_='%(levelname)s %(module)s %(funcName)s: %(lineno)d: %(message)s')¶
Behave exactly like
captureLogs()except intended for multiple loggers rather than one- Parameters:
- Returns:
Context manager yields all
logging_strict.tech_niques.logging_capture._LoggingWatcher. in a tuple. Order maintained- Return type:
Iterator[tuple[logging_strict.tech_niques.logging_capture._LoggingWatcher]]
- Raises:
AssertionError– Loggers and levels count mismatch