Skip to content

Commit

Permalink
Refactored logging to provide indented output
Browse files Browse the repository at this point in the history
  • Loading branch information
coordt committed Dec 10, 2023
1 parent 249a999 commit 4e68214
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 1 deletion.
73 changes: 73 additions & 0 deletions bumpversion/indented_logger.py
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
9 changes: 8 additions & 1 deletion bumpversion/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from click import UsageError, secho
from rich.logging import RichHandler

from bumpversion.indented_logger import IndentedLoggerAdapter

logger = logging.getLogger("bumpversion")

VERBOSITY = {
Expand All @@ -14,6 +16,11 @@
}


def get_indented_logger(name: str) -> "IndentedLoggerAdapter":
"""Get a logger with indentation."""
return IndentedLoggerAdapter(logging.getLogger(name))


def setup_logging(verbose: int = 0) -> None:
"""Configure the logging."""
logging.basicConfig(
Expand All @@ -26,7 +33,7 @@ def setup_logging(verbose: int = 0) -> None:
)
],
)
root_logger = logging.getLogger("")
root_logger = get_indented_logger("")
root_logger.setLevel(VERBOSITY.get(verbose, logging.DEBUG))


Expand Down
71 changes: 71 additions & 0 deletions tests/test_indented_logger.py
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")]

0 comments on commit 4e68214

Please sign in to comment.