2020-04-25 19:59:58 +02:00
|
|
|
"""
|
|
|
|
Contains a few logger utility functions and implementations.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import logging
|
|
|
|
from typing import Optional
|
|
|
|
|
2020-05-08 19:28:34 +02:00
|
|
|
from rich._log_render import LogRender
|
|
|
|
from rich.console import Console
|
2020-05-08 19:48:21 +02:00
|
|
|
from rich.style import Style
|
2020-05-08 19:28:34 +02:00
|
|
|
from rich.text import Text
|
2020-05-08 19:48:21 +02:00
|
|
|
from rich.theme import Theme
|
2020-04-25 19:59:58 +02:00
|
|
|
|
|
|
|
from .utils import PathLike, to_path
|
|
|
|
|
|
|
|
STYLE = "{"
|
|
|
|
FORMAT = "[{levelname:<7}] {message}"
|
|
|
|
DATE_FORMAT = "%F %T"
|
|
|
|
|
|
|
|
|
|
|
|
def enable_logging(name: str = "PFERD", level: int = logging.INFO) -> None:
|
|
|
|
"""
|
|
|
|
Enable and configure logging via the logging module.
|
|
|
|
"""
|
|
|
|
|
|
|
|
logger = logging.getLogger(name)
|
|
|
|
logger.setLevel(level)
|
2020-05-08 19:48:21 +02:00
|
|
|
logger.addHandler(RichLoggingHandler(level=level))
|
2020-04-25 19:59:58 +02:00
|
|
|
|
|
|
|
# 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.
|
|
|
|
logger.propagate = False
|
|
|
|
|
|
|
|
|
2020-04-25 20:05:19 +02:00
|
|
|
class FatalException(Exception):
|
|
|
|
"""
|
|
|
|
A fatal exception occurred. Recovery is not possible.
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
2020-05-08 19:28:34 +02:00
|
|
|
class RichLoggingHandler(logging.Handler):
|
|
|
|
"""
|
|
|
|
A logging handler that uses rich for highlighting
|
|
|
|
"""
|
|
|
|
|
2020-05-08 19:48:21 +02:00
|
|
|
def __init__(self, level: int) -> None:
|
2020-05-08 19:28:34 +02:00
|
|
|
super().__init__(level=level)
|
2020-05-08 19:48:21 +02:00
|
|
|
self.console = Console(theme=Theme({
|
|
|
|
"logging.level.warning": Style(color="yellow")
|
|
|
|
}))
|
2020-05-08 19:28:34 +02:00
|
|
|
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,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-04-25 19:59:58 +02:00
|
|
|
class PrettyLogger:
|
|
|
|
"""
|
|
|
|
A logger that prints some specially formatted log messages in color.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, logger: logging.Logger) -> None:
|
|
|
|
self.logger = logger
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _format_path(path: PathLike) -> str:
|
|
|
|
return repr(str(to_path(path)))
|
|
|
|
|
|
|
|
def error(self, message: str) -> None:
|
|
|
|
"""
|
|
|
|
Print an error message indicating some operation fatally failed.
|
|
|
|
"""
|
|
|
|
self.logger.error(
|
2020-05-08 19:28:34 +02:00
|
|
|
f"[bold red]{message}[/bold red]"
|
2020-04-25 19:59:58 +02:00
|
|
|
)
|
|
|
|
|
2020-04-25 20:11:51 +02:00
|
|
|
def warning(self, message: str) -> None:
|
2020-04-25 19:59:58 +02:00
|
|
|
"""
|
|
|
|
Print a warning message indicating some operation failed, but the error can be recovered
|
|
|
|
or ignored.
|
|
|
|
"""
|
|
|
|
self.logger.warning(
|
2020-05-08 19:28:34 +02:00
|
|
|
f"[bold yellow]{message}[/bold yellow]"
|
2020-04-25 19:59:58 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
def modified_file(self, path: PathLike) -> None:
|
|
|
|
"""
|
|
|
|
An existing file has changed.
|
|
|
|
"""
|
|
|
|
|
|
|
|
self.logger.info(
|
2020-05-08 19:28:34 +02:00
|
|
|
f"[bold magenta]Modified {self._format_path(path)}.[/bold magenta]"
|
2020-04-25 19:59:58 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
def new_file(self, path: PathLike) -> None:
|
|
|
|
"""
|
|
|
|
A new file has been downloaded.
|
|
|
|
"""
|
|
|
|
|
|
|
|
self.logger.info(
|
2020-05-08 19:28:34 +02:00
|
|
|
f"[bold green]Created {self._format_path(path)}.[/bold green]"
|
2020-04-25 19:59:58 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
def ignored_file(self, path: PathLike, reason: str) -> None:
|
|
|
|
"""
|
|
|
|
File was not downloaded or modified.
|
|
|
|
"""
|
|
|
|
|
|
|
|
self.logger.info(
|
2020-05-08 19:28:34 +02:00
|
|
|
f"[dim]Ignored {self._format_path(path)} "
|
|
|
|
f"([/dim]{reason}[dim]).[/dim]"
|
2020-04-25 19:59:58 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
def searching(self, path: PathLike) -> None:
|
|
|
|
"""
|
|
|
|
A crawler searches a particular object.
|
|
|
|
"""
|
|
|
|
|
|
|
|
self.logger.info(f"Searching {self._format_path(path)}")
|
|
|
|
|
|
|
|
def not_searching(self, path: PathLike, reason: str) -> None:
|
|
|
|
"""
|
|
|
|
A crawler does not search a particular object.
|
|
|
|
"""
|
|
|
|
|
|
|
|
self.logger.info(
|
2020-05-08 19:28:34 +02:00
|
|
|
f"[dim]Not searching {self._format_path(path)} "
|
|
|
|
f"([/dim]{reason}[dim]).[/dim]"
|
2020-04-25 19:59:58 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
def starting_synchronizer(
|
|
|
|
self,
|
|
|
|
target_directory: PathLike,
|
|
|
|
synchronizer_name: str,
|
|
|
|
subject: Optional[str] = None,
|
|
|
|
) -> None:
|
|
|
|
"""
|
|
|
|
A special message marking that a synchronizer has been started.
|
|
|
|
"""
|
|
|
|
|
|
|
|
subject_str = f"{subject} " if subject else ""
|
|
|
|
self.logger.info("")
|
|
|
|
self.logger.info((
|
2020-05-08 19:28:34 +02:00
|
|
|
f"[bold cyan]Synchronizing "
|
2020-04-25 19:59:58 +02:00
|
|
|
f"{subject_str}to {self._format_path(target_directory)} "
|
2020-05-08 19:28:34 +02:00
|
|
|
f"using the {synchronizer_name} synchronizer.[/bold cyan]"
|
2020-04-25 19:59:58 +02:00
|
|
|
))
|