""" Module for logging """ import logging from typing import Optional from rich import get_console from rich.console import ConsoleRenderable from rich.logging import RichHandler from rich.markup import escape from rich.text import Text from rich.theme import Theme from rich.traceback import install __all__ = [ "CRITICAL", "FATAL", "ERROR", "WARNING", "WARN", "INFO", "DEBUG", "MATCH", "NOTSET", "init_logging", "SpotdlFormatter", "LEVEL_TO_NAME", "NAME_TO_LEVEL", ] # https://github.com/python/cpython/blob/3.10/Lib/logging/__init__.py CRITICAL = 50 FATAL = CRITICAL ERROR = 40 WARNING = 30 WARN = WARNING INFO = 20 DEBUG = 10 MATCH = 5 NOTSET = 0 LEVEL_TO_NAME = { CRITICAL: "CRITICAL", ERROR: "ERROR", WARNING: "WARNING", INFO: "INFO", MATCH: "MATCH", DEBUG: "DEBUG", NOTSET: "NOTSET", } NAME_TO_LEVEL = { "CRITICAL": CRITICAL, "FATAL": FATAL, "ERROR": ERROR, "WARN": WARNING, "WARNING": WARNING, "INFO": INFO, "MATCH": MATCH, "DEBUG": DEBUG, "NOTSET": NOTSET, } THEME = Theme( { "bar.back": "grey23", "bar.complete": "rgb(165,66,129)", "bar.finished": "rgb(114,156,31)", "bar.pulse": "rgb(165,66,129)", "general": "green", "nonimportant": "rgb(40,100,40)", "progress.data.speed": "red", "progress.description": "none", "progress.download": "green", "progress.filesize": "green", "progress.filesize.total": "green", "progress.percentage": "green", "progress.remaining": "rgb(40,100,40)", "logging.level.debug": "blue", "logging.level.info": "green", "logging.level.warning": "yellow", "logging.level.error": "red", "logging.level.critical": "bold red", } ) class SpotdlFormatter(logging.Formatter): """ A custom logger for spotdl. """ def format(self, record: logging.LogRecord) -> str: """ Format a log record. """ result = escape(super().format(record)) msg = result if record.levelno == DEBUG: msg = f"[blue]{result}" if record.levelno == MATCH: msg = f"[magenta]{result}" if record.levelno == INFO: msg = f"[green]{result}" if record.levelno == WARNING: msg = f"[yellow]{result}" if record.levelno == ERROR: msg = f"[red]{result}" if record.levelno == CRITICAL: msg = f"[bold red]{result}" return msg class SpotdlHandler(RichHandler): """ A custom logging handler for spotdl. In this case, it's just a wrapper around the rich handler. To not highlight keywords in info messages """ def render_message( self, record: logging.LogRecord, message: str ) -> "ConsoleRenderable": """Render message text in to Text. ### Arguments - record: logging Record. - message: String containing log message. ### Returns - ConsoleRenderable: Renderable to display log message. """ use_markup = getattr(record, "markup", self.markup) message_text = Text.from_markup(message) if use_markup else Text(message) highlighter = getattr(record, "highlighter", self.highlighter) # Don't highlight info messages if highlighter and record.levelno != INFO: message_text = highlighter(message_text) if not hasattr(self, "keywords"): self.keywords = self.KEYWORDS # Don't highlight keywords in info messages if self.keywords and record.levelno != INFO: message_text.highlight_words(self.keywords, "logging.keyword") return message_text def init_logging(log_level: str, log_format: Optional[str] = None): """ Initialize logging for spotdl. ### Arguments - `console`: The console to use. - `log_level`: The log level to use. """ # Don't log too much logging.getLogger("requests").setLevel(logging.WARNING) logging.getLogger("urllib3").setLevel(logging.WARNING) logging.getLogger("spotipy").setLevel(logging.WARNING) logging.getLogger("asyncio").setLevel(logging.WARNING) logging.getLogger("syncedlyrics").setLevel(logging.WARNING) logging.getLogger("bandcamp_api").setLevel(logging.WARNING) logging.getLogger("beautifulsoup4").setLevel(logging.WARNING) logging.getLogger("pytube").setLevel(logging.ERROR) # Create console console = get_console() console.push_theme(THEME) # Add matching level loggers logging.addLevelName(MATCH, "MATCH") # Create a rich handler rich_handler = SpotdlHandler( show_time=log_level == "DEBUG", log_time_format="[%X]", omit_repeated_times=False, console=console, level=log_level, markup=True, show_path=log_level == "DEBUG", show_level=log_level == "DEBUG", rich_tracebacks=True, ) msg_format = "%(message)s" if log_format is None: if log_level == "DEBUG": msg_format = "%(threadName)s - %(message)s" else: msg_format = log_format # Add rich handler to spotdl logger rich_handler.setFormatter(SpotdlFormatter(msg_format)) # Create spotdl logger spotdl_logger = logging.getLogger("spotdl") # Setup spotdl logger spotdl_logger.setLevel(log_level) spotdl_logger.addHandler(rich_handler) # Install rich traceback handler install(show_locals=False, extra_lines=1, console=console)