-
-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactored logging to provide indented output
- Loading branch information
Showing
3 changed files
with
152 additions
and
1 deletion.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
"""A logger adapter that adds an indent to the beginning of each message.""" | ||
import logging | ||
from contextvars import ContextVar | ||
from typing import Any, MutableMapping, Optional, Tuple | ||
|
||
CURRENT_INDENT = ContextVar("current_indent", default=0) | ||
|
||
|
||
class IndentedLoggerAdapter(logging.LoggerAdapter): | ||
""" | ||
Logger adapter that adds an indent to the beginning of each message. | ||
Parameters: | ||
logger: The logger to adapt. | ||
extra: Extra values to add to the logging context. | ||
depth: The number of `indent_char` to generate for each indent level. | ||
indent_char: The character or string to use for indenting. | ||
reset: `True` if the indent level should be reset to zero. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
logger: logging.Logger, | ||
extra: Optional[dict] = None, | ||
depth: int = 2, | ||
indent_char: str = " ", | ||
reset: bool = False, | ||
): | ||
super().__init__(logger, extra or {}) | ||
self._depth = depth | ||
self._indent_char = indent_char | ||
if reset: | ||
self.reset() | ||
|
||
def indent(self, amount: int = 1) -> None: | ||
""" | ||
Increase the indent level by `amount`. | ||
""" | ||
CURRENT_INDENT.set(CURRENT_INDENT.get() + amount) | ||
|
||
def dedent(self, amount: int = 1) -> None: | ||
""" | ||
Decrease the indent level by `amount`. | ||
""" | ||
CURRENT_INDENT.set(max(0, CURRENT_INDENT.get() - amount)) | ||
|
||
def reset(self) -> None: | ||
""" | ||
Reset the indent level to zero. | ||
""" | ||
CURRENT_INDENT.set(0) | ||
|
||
@property | ||
def indent_str(self) -> str: | ||
""" | ||
The indent string. | ||
""" | ||
return (self._indent_char * self._depth) * CURRENT_INDENT.get() | ||
|
||
def process(self, msg: str, kwargs: Optional[MutableMapping[str, Any]]) -> Tuple[str, MutableMapping[str, Any]]: | ||
""" | ||
Process the message and add the indent. | ||
Args: | ||
msg: The logging message. | ||
kwargs: Keyword arguments passed to the logger. | ||
Returns: | ||
A tuple containing the message and keyword arguments. | ||
""" | ||
msg = self.indent_str + msg | ||
|
||
return msg, kwargs |
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,71 @@ | ||
import pytest | ||
|
||
from bumpversion.indented_logger import IndentedLoggerAdapter | ||
import logging | ||
|
||
|
||
class TestIndentedLogger: | ||
def test_does_not_indent_without_intent(self, caplog: pytest.LogCaptureFixture): | ||
caplog.set_level(logging.DEBUG) | ||
logger = IndentedLoggerAdapter(logging.getLogger(), reset=True) | ||
logger.debug("test debug") | ||
logger.info("test info") | ||
logger.warning("test warning") | ||
logger.error("test error") | ||
logger.critical("test critical") | ||
|
||
assert caplog.record_tuples == [ | ||
("root", 10, "test debug"), | ||
("root", 20, "test info"), | ||
("root", 30, "test warning"), | ||
("root", 40, "test error"), | ||
("root", 50, "test critical"), | ||
] | ||
|
||
def test_indents(self, caplog: pytest.LogCaptureFixture): | ||
caplog.set_level(logging.DEBUG) | ||
logger = IndentedLoggerAdapter(logging.getLogger(), reset=True) | ||
logger.info("test 1") | ||
logger.indent(2) | ||
logger.error("test %d", 2) | ||
logger.indent() | ||
logger.debug("test 3") | ||
logger.warning("test 4") | ||
logger.indent() | ||
logger.critical("test 5") | ||
logger.critical("test 6") | ||
|
||
assert caplog.record_tuples == [ | ||
("root", 20, "test 1"), | ||
("root", 40, " test 2"), | ||
("root", 10, " test 3"), | ||
("root", 30, " test 4"), | ||
("root", 50, " test 5"), | ||
("root", 50, " test 6"), | ||
] | ||
|
||
def test_dedents(self, caplog: pytest.LogCaptureFixture): | ||
caplog.set_level(logging.DEBUG) | ||
logger = IndentedLoggerAdapter(logging.getLogger(), reset=True) | ||
logger.indent(3) | ||
logger.info("test 1") | ||
logger.dedent(2) | ||
logger.error("test %d", 2) | ||
logger.dedent() | ||
logger.debug("test 3") | ||
logger.warning("test 4") | ||
|
||
assert caplog.record_tuples == [ | ||
("root", 20, " test 1"), | ||
("root", 40, " test 2"), | ||
("root", 10, "test 3"), | ||
("root", 30, "test 4"), | ||
] | ||
|
||
def test_cant_dedent_below_zero(self, caplog: pytest.LogCaptureFixture): | ||
caplog.set_level(logging.DEBUG) | ||
logger = IndentedLoggerAdapter(logging.getLogger()) | ||
logger.dedent(4) | ||
logger.info("test 1") | ||
|
||
assert caplog.record_tuples == [("root", 20, "test 1")] |