Use rich for log colorization

This commit is contained in:
I-Al-Istannen 2020-05-08 19:28:34 +02:00
parent 104b838aed
commit 2c48ab66d4
3 changed files with 54 additions and 35 deletions

View File

@ -5,8 +5,9 @@ Contains a few logger utility functions and implementations.
import logging import logging
from typing import Optional from typing import Optional
import colorama from rich._log_render import LogRender
from colorama import Fore, Style from rich.console import Console
from rich.text import Text
from .utils import PathLike, to_path from .utils import PathLike, to_path
@ -14,31 +15,20 @@ STYLE = "{"
FORMAT = "[{levelname:<7}] {message}" FORMAT = "[{levelname:<7}] {message}"
DATE_FORMAT = "%F %T" DATE_FORMAT = "%F %T"
FORMATTER = logging.Formatter(
fmt=FORMAT,
datefmt=DATE_FORMAT,
style=STYLE,
)
def enable_logging(name: str = "PFERD", level: int = logging.INFO) -> None: def enable_logging(name: str = "PFERD", level: int = logging.INFO) -> None:
""" """
Enable and configure logging via the logging module. Enable and configure logging via the logging module.
""" """
handler = logging.StreamHandler()
handler.setFormatter(FORMATTER)
logger = logging.getLogger(name) logger = logging.getLogger(name)
logger.setLevel(level) logger.setLevel(level)
logger.addHandler(handler) logger.addHandler(RichLoggingHandler())
# This should be logged by our own handler, and not the root logger's # This should be logged by our own handler, and not the root logger's
# default handler, so we don't pass it on to the root logger. # default handler, so we don't pass it on to the root logger.
logger.propagate = False logger.propagate = False
colorama.init()
class FatalException(Exception): class FatalException(Exception):
""" """
@ -46,6 +36,36 @@ class FatalException(Exception):
""" """
class RichLoggingHandler(logging.Handler):
"""
A logging handler that uses rich for highlighting
"""
def __init__(self, level: int = logging.NOTSET, console: Optional[Console] = None) -> None:
super().__init__(level=level)
self.console = Console() if console is None else console
self._log_render = LogRender(show_level=True, show_time=False, show_path=False)
def emit(self, record: logging.LogRecord) -> None:
"""
Invoked by logging.
"""
log_style = f"logging.level.{record.levelname.lower()}"
message = self.format(record)
level = Text()
level.append(record.levelname, log_style)
message_text = Text.from_markup(message)
self.console.print(
self._log_render(
self.console,
[message_text],
level=level,
)
)
class PrettyLogger: class PrettyLogger:
""" """
A logger that prints some specially formatted log messages in color. A logger that prints some specially formatted log messages in color.
@ -63,7 +83,7 @@ class PrettyLogger:
Print an error message indicating some operation fatally failed. Print an error message indicating some operation fatally failed.
""" """
self.logger.error( self.logger.error(
f"{Fore.RED}{Style.BRIGHT}{message}{Style.RESET_ALL}" f"[bold red]{message}[/bold red]"
) )
def warning(self, message: str) -> None: def warning(self, message: str) -> None:
@ -72,7 +92,7 @@ class PrettyLogger:
or ignored. or ignored.
""" """
self.logger.warning( self.logger.warning(
f"{Fore.YELLOW}{Style.BRIGHT}{message}{Style.RESET_ALL}" f"[bold yellow]{message}[/bold yellow]"
) )
def modified_file(self, path: PathLike) -> None: def modified_file(self, path: PathLike) -> None:
@ -81,7 +101,7 @@ class PrettyLogger:
""" """
self.logger.info( self.logger.info(
f"{Fore.MAGENTA}{Style.BRIGHT}Modified {self._format_path(path)}.{Style.RESET_ALL}" f"[bold magenta]Modified {self._format_path(path)}.[/bold magenta]"
) )
def new_file(self, path: PathLike) -> None: def new_file(self, path: PathLike) -> None:
@ -90,7 +110,7 @@ class PrettyLogger:
""" """
self.logger.info( self.logger.info(
f"{Fore.GREEN}{Style.BRIGHT}Created {self._format_path(path)}.{Style.RESET_ALL}" f"[bold green]Created {self._format_path(path)}.[/bold green]"
) )
def ignored_file(self, path: PathLike, reason: str) -> None: def ignored_file(self, path: PathLike, reason: str) -> None:
@ -99,8 +119,8 @@ class PrettyLogger:
""" """
self.logger.info( self.logger.info(
f"{Style.DIM}Ignored {self._format_path(path)} " f"[dim]Ignored {self._format_path(path)} "
f"({Style.NORMAL}{reason}{Style.DIM}).{Style.RESET_ALL}" f"([/dim]{reason}[dim]).[/dim]"
) )
def searching(self, path: PathLike) -> None: def searching(self, path: PathLike) -> None:
@ -116,8 +136,8 @@ class PrettyLogger:
""" """
self.logger.info( self.logger.info(
f"{Style.DIM}Not searching {self._format_path(path)} " f"[dim]Not searching {self._format_path(path)} "
f"({Style.NORMAL}{reason}{Style.DIM}).{Style.RESET_ALL}" f"([/dim]{reason}[dim]).[/dim]"
) )
def starting_synchronizer( def starting_synchronizer(
@ -133,7 +153,7 @@ class PrettyLogger:
subject_str = f"{subject} " if subject else "" subject_str = f"{subject} " if subject else ""
self.logger.info("") self.logger.info("")
self.logger.info(( self.logger.info((
f"{Fore.CYAN}{Style.BRIGHT}Synchronizing " f"[bold cyan]Synchronizing "
f"{subject_str}to {self._format_path(target_directory)} " f"{subject_str}to {self._format_path(target_directory)} "
f"using the {synchronizer_name} synchronizer.{Style.RESET_ALL}" f"using the {synchronizer_name} synchronizer.[/bold cyan]"
)) ))

View File

@ -3,5 +3,5 @@ disallow_untyped_defs = True
disallow_incomplete_defs = True disallow_incomplete_defs = True
no_implicit_optional = True no_implicit_optional = True
[mypy-colorama,bs4] [mypy-rich.*,bs4]
ignore_missing_imports = True ignore_missing_imports = True

View File

@ -1,15 +1,14 @@
from setuptools import setup, find_packages from setuptools import find_packages, setup
setup( setup(
name="PFERD", name="PFERD",
version="1.1.8", version="1.1.8",
packages=find_packages(), packages=find_packages(),
install_requires=[ install_requires=[
"requests>=2.21.0", "requests>=2.21.0",
"beautifulsoup4>=4.7.1", "beautifulsoup4>=4.7.1",
"colorama>=0.4.1", "rich>=1.0.0"
"rich>=1.0.0" ],
],
) )
# When updating the version, also: # When updating the version, also: