-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added
Captured
and Logot.capture()
, enabling custom log capture b…
…ackends (#38) - Added `Captured`, representing an abstract captured log. - Added `_format` internal module, providing shared formatting logic for `__str__` implementations. - Bulk-renaming to represent decoupling from `logging` module. 😅 - Removed log de-duplication, since it's non-portable and shouldn't be a problem for users / is actually a feature. - Added "Log capturing" docs page. This is needed by #28.
- Loading branch information
Showing
14 changed files
with
357 additions
and
180 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
Log capturing | ||
============= | ||
|
||
.. currentmodule:: logot | ||
|
||
:mod:`logot` makes it easy to capture logs from the stdlib :mod:`logging` module: | ||
|
||
.. code:: python | ||
with Logot().capturing() as logot: | ||
app.start() | ||
logot.wait_for(logged.info("App started")) | ||
.. note:: | ||
|
||
If using :mod:`pytest`, you can probably just use the pre-configured ``logot`` fixture included in the bundled | ||
:doc:`pytest plugin <pytest>` and skip manually configuring log capture. 💪 | ||
|
||
|
||
Capturing :mod:`logging` logs | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
The :meth:`Logot.capturing` method defaults to capturing **all** records from the root logger. Customize this with the | ||
``level`` and ``logger`` arguments to :meth:`Logot.capturing`: | ||
|
||
.. code:: python | ||
with Logot().capturing(level=logging.WARNING, logger="app") as logot: | ||
app.start() | ||
logot.wait_for(logged.info("App started")) | ||
For advanced use-cases, multiple :meth:`Logot.capturing` calls on the same :class:`Logot` instance are supported. Be | ||
careful to avoid capturing duplicate logs with overlapping calls to :meth:`Logot.capturing`! | ||
|
||
.. seealso:: | ||
|
||
See :class:`Logot` and :meth:`Logot.capturing` API reference. | ||
|
||
|
||
.. _captured-3rd-party: | ||
|
||
Capturing 3rd-party logs | ||
~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
Any 3rd-party logging library can be integrated with :mod:`logot` by sending :class:`Captured` logs to | ||
:meth:`Logot.capture`: | ||
|
||
.. code:: python | ||
def on_foo_log(logot: Logot, record: FooRecord) -> None: | ||
logot.capture(Captured(record.levelno, record.msg)) | ||
foo_logger.add_handler(on_foo_log) | ||
.. note:: | ||
|
||
Using a context manager to set up and tear down log capture for every test run is *highly recommended*! | ||
|
||
.. seealso:: | ||
|
||
See :class:`Captured` and :meth:`Logot.capture` API reference. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -150,6 +150,7 @@ Learn more about :mod:`logot` with the following guides: | |
|
||
match | ||
logged | ||
captured | ||
pytest | ||
unittest | ||
api |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
from __future__ import annotations | ||
|
||
from logot._captured import Captured as Captured | ||
from logot._logged import Logged as Logged | ||
from logot._logot import Logot as Logot |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
from __future__ import annotations | ||
|
||
import logging | ||
from typing import Final | ||
|
||
from logot._format import format_log | ||
from logot._validate import validate_levelno | ||
|
||
|
||
class Captured: | ||
""" | ||
A captured log record. | ||
Send :class:`Captured` logs to :meth:`Logot.capture` to integrate with | ||
:ref:`3rd-party logging frameworks <captured-3rd-party>` | ||
.. note:: | ||
This class is for integration with :ref:`3rd-party logging frameworks <captured-3rd-party>`. It is not generally | ||
used when writing tests. | ||
.. seealso:: | ||
See :ref:`captured-3rd-party` usage guide. | ||
:param level: The log level (e.g. :data:`logging.DEBUG`) or string name (e.g. ``"DEBUG"``). | ||
:param msg: The log message. | ||
""" | ||
|
||
__slots__ = ("levelno", "msg") | ||
|
||
levelno: Final[int] | ||
""" | ||
The integer log level (e.g. :data:`logging.DEBUG`). | ||
""" | ||
|
||
msg: Final[str] | ||
""" | ||
The log message. | ||
""" | ||
|
||
def __init__(self, level: int | str, msg: str) -> None: | ||
self.levelno = validate_levelno(level) | ||
self.msg = msg | ||
|
||
def __eq__(self, other: object) -> bool: | ||
return isinstance(other, Captured) and other.levelno == self.levelno and other.msg == self.msg | ||
|
||
def __repr__(self) -> str: | ||
return f"Captured({logging.getLevelName(self.levelno)!r}, {self.msg!r})" | ||
|
||
def __str__(self) -> str: | ||
return format_log(self.levelno, self.msg) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from __future__ import annotations | ||
|
||
import logging | ||
|
||
|
||
def format_log(levelno: int, msg: str) -> str: | ||
return f"[{logging.getLevelName(levelno)}] {msg}" |
Oops, something went wrong.