Skip to content

Commit 4e68214

Browse files
committed
Refactored logging to provide indented output
1 parent 249a999 commit 4e68214

File tree

3 files changed

+152
-1
lines changed

3 files changed

+152
-1
lines changed

bumpversion/indented_logger.py

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""A logger adapter that adds an indent to the beginning of each message."""
2+
import logging
3+
from contextvars import ContextVar
4+
from typing import Any, MutableMapping, Optional, Tuple
5+
6+
CURRENT_INDENT = ContextVar("current_indent", default=0)
7+
8+
9+
class IndentedLoggerAdapter(logging.LoggerAdapter):
10+
"""
11+
Logger adapter that adds an indent to the beginning of each message.
12+
13+
Parameters:
14+
logger: The logger to adapt.
15+
extra: Extra values to add to the logging context.
16+
depth: The number of `indent_char` to generate for each indent level.
17+
indent_char: The character or string to use for indenting.
18+
reset: `True` if the indent level should be reset to zero.
19+
"""
20+
21+
def __init__(
22+
self,
23+
logger: logging.Logger,
24+
extra: Optional[dict] = None,
25+
depth: int = 2,
26+
indent_char: str = " ",
27+
reset: bool = False,
28+
):
29+
super().__init__(logger, extra or {})
30+
self._depth = depth
31+
self._indent_char = indent_char
32+
if reset:
33+
self.reset()
34+
35+
def indent(self, amount: int = 1) -> None:
36+
"""
37+
Increase the indent level by `amount`.
38+
"""
39+
CURRENT_INDENT.set(CURRENT_INDENT.get() + amount)
40+
41+
def dedent(self, amount: int = 1) -> None:
42+
"""
43+
Decrease the indent level by `amount`.
44+
"""
45+
CURRENT_INDENT.set(max(0, CURRENT_INDENT.get() - amount))
46+
47+
def reset(self) -> None:
48+
"""
49+
Reset the indent level to zero.
50+
"""
51+
CURRENT_INDENT.set(0)
52+
53+
@property
54+
def indent_str(self) -> str:
55+
"""
56+
The indent string.
57+
"""
58+
return (self._indent_char * self._depth) * CURRENT_INDENT.get()
59+
60+
def process(self, msg: str, kwargs: Optional[MutableMapping[str, Any]]) -> Tuple[str, MutableMapping[str, Any]]:
61+
"""
62+
Process the message and add the indent.
63+
64+
Args:
65+
msg: The logging message.
66+
kwargs: Keyword arguments passed to the logger.
67+
68+
Returns:
69+
A tuple containing the message and keyword arguments.
70+
"""
71+
msg = self.indent_str + msg
72+
73+
return msg, kwargs

bumpversion/ui.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from click import UsageError, secho
66
from rich.logging import RichHandler
77

8+
from bumpversion.indented_logger import IndentedLoggerAdapter
9+
810
logger = logging.getLogger("bumpversion")
911

1012
VERBOSITY = {
@@ -14,6 +16,11 @@
1416
}
1517

1618

19+
def get_indented_logger(name: str) -> "IndentedLoggerAdapter":
20+
"""Get a logger with indentation."""
21+
return IndentedLoggerAdapter(logging.getLogger(name))
22+
23+
1724
def setup_logging(verbose: int = 0) -> None:
1825
"""Configure the logging."""
1926
logging.basicConfig(
@@ -26,7 +33,7 @@ def setup_logging(verbose: int = 0) -> None:
2633
)
2734
],
2835
)
29-
root_logger = logging.getLogger("")
36+
root_logger = get_indented_logger("")
3037
root_logger.setLevel(VERBOSITY.get(verbose, logging.DEBUG))
3138

3239

tests/test_indented_logger.py

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import pytest
2+
3+
from bumpversion.indented_logger import IndentedLoggerAdapter
4+
import logging
5+
6+
7+
class TestIndentedLogger:
8+
def test_does_not_indent_without_intent(self, caplog: pytest.LogCaptureFixture):
9+
caplog.set_level(logging.DEBUG)
10+
logger = IndentedLoggerAdapter(logging.getLogger(), reset=True)
11+
logger.debug("test debug")
12+
logger.info("test info")
13+
logger.warning("test warning")
14+
logger.error("test error")
15+
logger.critical("test critical")
16+
17+
assert caplog.record_tuples == [
18+
("root", 10, "test debug"),
19+
("root", 20, "test info"),
20+
("root", 30, "test warning"),
21+
("root", 40, "test error"),
22+
("root", 50, "test critical"),
23+
]
24+
25+
def test_indents(self, caplog: pytest.LogCaptureFixture):
26+
caplog.set_level(logging.DEBUG)
27+
logger = IndentedLoggerAdapter(logging.getLogger(), reset=True)
28+
logger.info("test 1")
29+
logger.indent(2)
30+
logger.error("test %d", 2)
31+
logger.indent()
32+
logger.debug("test 3")
33+
logger.warning("test 4")
34+
logger.indent()
35+
logger.critical("test 5")
36+
logger.critical("test 6")
37+
38+
assert caplog.record_tuples == [
39+
("root", 20, "test 1"),
40+
("root", 40, " test 2"),
41+
("root", 10, " test 3"),
42+
("root", 30, " test 4"),
43+
("root", 50, " test 5"),
44+
("root", 50, " test 6"),
45+
]
46+
47+
def test_dedents(self, caplog: pytest.LogCaptureFixture):
48+
caplog.set_level(logging.DEBUG)
49+
logger = IndentedLoggerAdapter(logging.getLogger(), reset=True)
50+
logger.indent(3)
51+
logger.info("test 1")
52+
logger.dedent(2)
53+
logger.error("test %d", 2)
54+
logger.dedent()
55+
logger.debug("test 3")
56+
logger.warning("test 4")
57+
58+
assert caplog.record_tuples == [
59+
("root", 20, " test 1"),
60+
("root", 40, " test 2"),
61+
("root", 10, "test 3"),
62+
("root", 30, "test 4"),
63+
]
64+
65+
def test_cant_dedent_below_zero(self, caplog: pytest.LogCaptureFixture):
66+
caplog.set_level(logging.DEBUG)
67+
logger = IndentedLoggerAdapter(logging.getLogger())
68+
logger.dedent(4)
69+
logger.info("test 1")
70+
71+
assert caplog.record_tuples == [("root", 20, "test 1")]

0 commit comments

Comments
 (0)