diff --git a/.changelog/_unreleased.toml b/.changelog/_unreleased.toml new file mode 100644 index 00000000..e2396632 --- /dev/null +++ b/.changelog/_unreleased.toml @@ -0,0 +1,5 @@ + +[[entries]] +id = "b6fb5e00-0bed-4e07-8cf3-c16c394d40fe" +type = "improvement" +description = "Replace `rich` logger with `loguru` which produces nicer log formatting and requires less setup" diff --git a/kraken-build/pyproject.toml b/kraken-build/pyproject.toml index 6e3d01e3..b5314163 100644 --- a/kraken-build/pyproject.toml +++ b/kraken-build/pyproject.toml @@ -47,13 +47,13 @@ networkx = "^3.1" packaging = "^23.1" pex = "^2.1.156" python = ">=3.10" -rich = "^13.4.2" termcolor = "^2.3.0" tomli = "^2.0.1" tomli-w = "^1.0.0" tomlkit = "^0.13.0" typeapi = "^2.0.0" typing-extensions = ">=4.6.0" +loguru = "^0.7.2" [tool.poetry.dev-dependencies] localimport = "^1.7.6" diff --git a/kraken-build/src/kraken/common/_option_sets.py b/kraken-build/src/kraken/common/_option_sets.py index 8e7536fb..0bf3f535 100644 --- a/kraken-build/src/kraken/common/_option_sets.py +++ b/kraken-build/src/kraken/common/_option_sets.py @@ -1,9 +1,11 @@ import argparse +import inspect import logging +import sys from dataclasses import dataclass from typing import ClassVar -logger = logging.getLogger(__name__) +from loguru import logger @dataclass(frozen=True) @@ -41,25 +43,40 @@ def collect(cls, args: argparse.Namespace) -> "LoggingOptions": ) def init_logging(self, force_color: bool = False) -> None: - import logging - - from rich.console import Console - from rich.logging import RichHandler - verbosity = self.verbosity - self.quietness if verbosity > 1: - level = logging.DEBUG + level = "DEBUG" elif verbosity > 0: - level = logging.INFO + level = "INFO" elif verbosity == 0: - level = logging.WARNING + level = "WARNING" elif verbosity < 0: - level = logging.ERROR + level = "ERROR" else: assert False, verbosity - console = Console(force_terminal=True if force_color else None) - logging.basicConfig(level=level, format="%(message)s", handlers=[RichHandler(console=console)]) + # Intercept standard logs and send them to Loguru. + class InterceptHandler(logging.Handler): + def emit(self, record: logging.LogRecord) -> None: + # Get corresponding Loguru level if it exists. + level: str | int + try: + level = logger.level(record.levelname).name + except ValueError: + level = record.levelno + + # Find caller from where originated the logged message. + frame, depth = inspect.currentframe(), 0 + while frame and (depth == 0 or frame.f_code.co_filename == logging.__file__): + frame = frame.f_back + depth += 1 + + logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage()) + + logging.basicConfig(handlers=[InterceptHandler()], level=0, force=True) + + logger.remove() + logger.add(sys.stderr, level=level) @dataclass diff --git a/kraken-build/src/kraken/common/findpython.py b/kraken-build/src/kraken/common/findpython.py index 1775f754..1c0b1b13 100644 --- a/kraken-build/src/kraken/common/findpython.py +++ b/kraken-build/src/kraken/common/findpython.py @@ -10,13 +10,10 @@ import sys from collections.abc import Iterable, Iterator, Sequence from pathlib import Path -from typing import TYPE_CHECKING, ClassVar +from typing import ClassVar from typing_extensions import NotRequired, TypedDict -if TYPE_CHECKING: - import rich.table - logger = logging.getLogger(__name__) DEFAULT_CACHE_PATH = Path("~/.cache/krakenw/python-interpreters.json") @@ -217,22 +214,21 @@ def evaluate_candidates( return interpreters -def build_rich_table(interpreters: Iterable[Interpreter]) -> rich.table.Table: +def print_interpreters(interpreters: Iterable[Interpreter]) -> None: """ - Gets a table of all viable Python interpreters on the system. - - Requires that the `rich` package is installed. + Prints the interpeter information to stdout, colored. """ - import rich.table + from kraken.common import Color, colored - tb = rich.table.Table("Path", "Version") for interpreter in interpreters: + prefix = " " version = interpreter["version"] + version_color: Color = "grey" if interpreter.get("selected"): - version += " *" - tb.add_row(interpreter["path"], version) - return tb + prefix = " *" + version_color = "cyan" + print(colored(prefix, "grey"), colored(interpreter["path"], "green"), f"({colored(version, version_color)})") def match_version_constraint(constraint: str, version: str) -> bool: @@ -248,19 +244,6 @@ def match_version_constraint(constraint: str, version: str) -> bool: def main() -> None: import argparse - try: - import rich - import rich.table - - def tabulate(interpreters: Iterable[Interpreter]) -> None: - rich.print(build_rich_table(interpreters)) - - except ImportError: - - def tabulate(interpreters: Iterable[Interpreter]) -> None: - for interpreter in interpreters: - print(interpreter) - parser = argparse.ArgumentParser() parser.add_argument("-c", "--constraint", help="Find a Python interpreter with the given constraint.") args = parser.parse_args() @@ -272,7 +255,7 @@ def tabulate(interpreters: Iterable[Interpreter]) -> None: if match_version_constraint(args.constraint, interpreter["version"]): interpreter["selected"] = True - tabulate(interpreters) + print_interpreters(interpreters) if __name__ == "__main__": diff --git a/kraken-build/src/kraken/core/system/task.py b/kraken-build/src/kraken/core/system/task.py index fc4608c3..d5605e2e 100644 --- a/kraken-build/src/kraken/core/system/task.py +++ b/kraken-build/src/kraken/core/system/task.py @@ -14,6 +14,7 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, ForwardRef, Generic, Literal, TypeVar, cast, overload +from loguru import logger from kraken.common import Supplier from kraken.core.address import Address @@ -32,7 +33,6 @@ T = TypeVar("T") T_Task = TypeVar("T_Task", bound="Task") -logger = logging.getLogger(__name__) @dataclasses.dataclass @@ -247,7 +247,7 @@ def add_tag(self, name: str, *, reason: str, origin: str | None = None) -> None: if name not in self.__tags: self.__tags[name] = set() - logger.debug("Adding tag %r (reason: %r, origin: %r) to %s", name, reason, origin, self.address) + logger.debug("Adding tag {!r} (reason: {!r}, origin: {!r}) to {}", name, reason, origin, self.address) self.__tags[name].add(TaskTag(name, reason, origin)) def remove_tag(self, tag: TaskTag) -> None: @@ -258,10 +258,10 @@ def remove_tag(self, tag: TaskTag) -> None: try: self.__tags[tag.name].discard(tag) except KeyError: - logger.debug("Attempted to remove tag %r from %s, but it does not exist", tag, self.address) + logger.debug("Attempted to remove tag {!r} from {}, but it does not exist", tag, self.address) pass else: - logger.debug("Removed tag %r from %s", tag, self.address) + logger.debug("Removed tag {!r} from {}", tag, self.address) def get_tags(self, name: str) -> Collection[TaskTag]: """ @@ -539,7 +539,7 @@ def __del__(self) -> None: pass else: logger.warning( - 'BackgroundTask.teardown() did not get called on task "%s". This may cause some issues, such ' + 'BackgroundTask.teardown() did not get called on task "{}". This may cause some issues, such ' "as an error during serialization or zombie processes.", self.address, ) diff --git a/kraken-wrapper/pyproject.toml b/kraken-wrapper/pyproject.toml index 844e64cb..09e3797a 100644 --- a/kraken-wrapper/pyproject.toml +++ b/kraken-wrapper/pyproject.toml @@ -23,6 +23,7 @@ Repository = "https://github.com/kraken-build/kraken-build/" [tool.poetry.dependencies] kraken-build = "0.37.3" +loguru = "^0.7.2" packaging = "^23.1" python = ">=3.10" termcolor = "^2.3.0" diff --git a/kraken-wrapper/src/kraken/wrapper/main.py b/kraken-wrapper/src/kraken/wrapper/main.py index 1d947e93..d70a0dbe 100644 --- a/kraken-wrapper/src/kraken/wrapper/main.py +++ b/kraken-wrapper/src/kraken/wrapper/main.py @@ -228,7 +228,6 @@ def auth_check(auth: AuthModel, args: AuthOptions, host: str, username: str, pas def list_pythons(prog: str, argv: list[str]) -> NoReturn: - import rich from kraken.common import findpython if argv: @@ -236,8 +235,7 @@ def list_pythons(prog: str, argv: list[str]) -> NoReturn: sys.exit(1) interpreters = findpython.evaluate_candidates(findpython.get_candidates(), findpython.InterpreterVersionCache()) - table = findpython.build_rich_table(interpreters) - rich.print(table) + findpython.print_interpreters(interpreters) sys.exit(0)