Source code for curldl.util.log

"""Logging and tracing utilities for internal use"""
from __future__ import annotations

import logging
import sys
import threading
import traceback
import types
from typing import Type

log = logging.getLogger(__name__)


[docs] class Log: """Logging and tracing utilities"""
[docs] @classmethod def setup_exception_logging_hooks(cls) -> None: """Assigns exception logging hooks: :func:`sys.excepthook`, :func:`sys.unraisablehook`, :func:`threading.excepthook`.""" sys.excepthook = cls.trace_unhandled_exception sys.unraisablehook = cls.trace_unraisable_exception threading.excepthook = cls.trace_thread_exception
[docs] @classmethod def trace_unhandled_exception( cls, exc_type: Type[BaseException], exc: BaseException, trace_back: types.TracebackType | None, ) -> None: """Top-level logger for unhandled exceptions, can be assigned to :func:`sys.excepthook`. The exception is logged at ``CRITICAL`` level, and traceback at ``DEBUG`` level. :param exc_type: exception type (expected: ``exc.__class__``) :param exc: exception object :param trace_back: exception traceback (expected: ``exc.__traceback__``) """ cls._trace_exception_details( loglevel=logging.CRITICAL, exc=exc, exc_type=exc_type, trace_back=trace_back )
[docs] @classmethod def trace_unraisable_exception(cls, exc_info: sys.UnraisableHookArgs) -> None: """Top-level logger for unraisable exceptions, can be assigned to :func:`sys.unraisablehook` :param exc_info: container with unraisable exception attributes """ msg = ( f"{exc_info.err_msg}: {exc_info.object}" if exc_info.err_msg else str(exc_info.object) ) cls._trace_exception_details( loglevel=logging.ERROR, exc=exc_info.exc_value, exc_type=exc_info.exc_type, trace_back=exc_info.exc_traceback, msg=msg, )
[docs] @classmethod def trace_thread_exception(cls, exc_info: threading.ExceptHookArgs) -> None: """Top-level logger for exceptions raised by :meth:`threading.Thread.start` :param exc_info: container thread exception attributes """ msg = str(exc_info.thread) if exc_info.thread else None cls._trace_exception_details( loglevel=logging.ERROR, exc=exc_info.exc_value, exc_type=exc_info.exc_type, trace_back=exc_info.exc_traceback, msg=msg, )
[docs] @classmethod def trace_exception(cls, exc: BaseException, msg: str) -> None: """Logging helper to trace an exception. The exception is logged at ``ERROR`` level, ad traceback at ``DEBUG`` level. :param exc: exception object :param msg: message to prepend when logging the exception """ cls._trace_exception_details( loglevel=logging.ERROR, exc=exc, exc_type=exc.__class__, trace_back=exc.__traceback__, msg=msg, )
[docs] @staticmethod def _trace_exception_details( *, loglevel: int, exc: BaseException | None, exc_type: Type[BaseException], trace_back: types.TracebackType | None, msg: str | None = None, ) -> None: """Generic logger for exception details :param loglevel: logging level for main message, auxiliary message is logged at ``DEBUG`` level :param exc: exception object :param exc_type: exception type (class) :param trace_back: exception traceback :param msg: extra exception message, prepended to main message if specified """ msg_prefix = f"{msg}: " if msg else "" log.log(loglevel, "%s%s: %s", msg_prefix, exc_type.__name__, exc) if log.isEnabledFor(logging.DEBUG): log.debug( "".join( traceback.format_exception(exc_type, value=exc, tb=trace_back) ).rstrip("\n") )