Source code for f_lib.logging._logger

"""Custom logger."""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any, Self, TypeAlias, cast

from pydantic import BaseModel, ConfigDict

from ._log_level import LogLevel

if TYPE_CHECKING:
    from collections.abc import Mapping
    from types import TracebackType

_SysExcInfoType: TypeAlias = (
    "tuple[type[BaseException], BaseException, TracebackType | None] | tuple[None, None, None]"
)
_ExcInfoType: TypeAlias = "None | bool | _SysExcInfoType | BaseException"


logging.addLevelName(LogLevel.VERBOSE, LogLevel.VERBOSE.name)
logging.addLevelName(LogLevel.NOTICE, LogLevel.NOTICE.name)
logging.addLevelName(LogLevel.SUCCESS, LogLevel.SUCCESS.name)


[docs] class LoggerSettings(BaseModel): """Logger settings.""" model_config = ConfigDict(extra="forbid") markup: bool = False """Enable rich markup (if available)."""
[docs] class Logger(logging.Logger): """Customized subclass of :class:`logging.Logger`.""" settings: LoggerSettings """Custom logger settings."""
[docs] def __init__( self, name: str, level: LogLevel = LogLevel.NOTSET, *, settings: LoggerSettings | None = None, ) -> None: """Initialize the logger with a name, level, and settings.""" super().__init__(name, level) if settings is None: self.settings = LoggerSettings() else: self.settings = settings
def _log( self, level: int, msg: object, args: tuple[object, ...] | Mapping[str, object], exc_info: _ExcInfoType | None = None, extra: Mapping[str, Any] | None = None, stack_info: bool = False, # noqa: FBT001, FBT002 stacklevel: int = 1, ) -> None: """Wrap log messages with color.""" local_extra = self.settings.model_dump() if extra: local_extra.update(extra) stacklevel += 1 super()._log( level, msg, args, exc_info=exc_info, extra=local_extra, stack_info=stack_info, stacklevel=stacklevel, )
[docs] def notice( self, msg: Exception | str, *args: object, exc_info: bool = False, extra: Mapping[str, object] | None = None, **kwargs: Any, # noqa: ANN401 ) -> None: """Log 'msg % args' with severity ``NOTICE``. Args: msg: String template or exception to use for the log record. *args: Replacement values for the string template. exc_info: Include exception traceback in the log record. extra: Dictionary to populated additional information in the log record. **kwargs: Arbitrary keyword arguments """ if self.isEnabledFor(LogLevel.NOTICE): self._log(LogLevel.NOTICE, msg, args, exc_info=exc_info, extra=extra, **kwargs)
[docs] def success( self, msg: Exception | str, *args: object, exc_info: bool = False, extra: Mapping[str, object] | None = None, **kwargs: Any, # noqa: ANN401 ) -> None: """Log 'msg % args' with severity ``SUCCESS``. Args: msg: String template or exception to use for the log record. *args: Replacement values for the string template. exc_info: Include exception traceback in the log record. extra: Dictionary to populated additional information in the log record. **kwargs: Arbitrary keyword arguments """ if self.isEnabledFor(LogLevel.SUCCESS): self._log(LogLevel.SUCCESS, msg, args, exc_info=exc_info, extra=extra, **kwargs)
[docs] def verbose( self, msg: Exception | str, *args: object, exc_info: bool = False, extra: Mapping[str, object] | None = None, **kwargs: Any, # noqa: ANN401 ) -> None: """Log 'msg % args' with severity ``VERBOSE``. Args: msg: String template or exception to use for the log record. *args: Replacement values for the string template. exc_info: Include exception traceback in the log record. extra: Dictionary to populated additional information in the log record. **kwargs: Arbitrary keyword arguments """ if self.isEnabledFor(LogLevel.VERBOSE): self._log(LogLevel.VERBOSE, msg, args, exc_info=exc_info, extra=extra, **kwargs)
[docs] @classmethod def get_logger(cls: type[Self], name: str, *, markup: bool = True) -> Self: """Return a logger with the specified name, creating it if necessary. This class method replaces :func:`logging.getLogger` to provide the correct logger type, only needing to correct it once. However, this wrapper requires a name to be provided to avoid implicit use of the root logger. .. rubric:: Example .. code-block:: python LOGGER = Logger.get_logger(__name__) LOGGER.info("usage example") Args: name: Name of the logger. It is recommended to use ``__name__`` here in most cases. level: After creating the logger (if needed), set it's level. markup: Whether to enable rich markup. Returns: Custom logger object. """ rv = cast("Self", cls.manager.getLogger(name)) rv.settings.markup = markup return rv