351 lines
9.8 KiB
Python
351 lines
9.8 KiB
Python
"""
|
|
This module provides a base class `LoggerBase` and a derived class `Logged`
|
|
for adding logging capabilities to classes. The `LoggerBase` class expects
|
|
a `logger` attribute to be a `logging.Logger` or compatible instance and
|
|
provides methods for logging at various levels. The `Logged` class
|
|
automatically adds a named logger to the class.
|
|
|
|
Classes:
|
|
LoggerBase:
|
|
A base class that adds logging utilities to a class.
|
|
Logged:
|
|
A derived class that automatically adds a named logger to a class.
|
|
|
|
Example:
|
|
>>> class MyClass(Logged):
|
|
... def __init__(self):
|
|
... Logged.__init__(self)
|
|
|
|
>>> my_class = MyClass()
|
|
>>> my_class.debug('debug')
|
|
>>> my_class.info('info')
|
|
>>> my_class.warning('warning')
|
|
>>> my_class.error('error')
|
|
>>> my_class.exception('exception')
|
|
>>> my_class.log(0, 'log')
|
|
"""
|
|
|
|
import abc
|
|
import logging
|
|
|
|
from . import decorators
|
|
|
|
__all__ = ['Logged']
|
|
|
|
from . import types
|
|
|
|
# From the logging typeshed, converted to be compatible with Python 3.8
|
|
# https://github.com/python/typeshed/blob/main/stdlib/logging/__init__.pyi
|
|
_ExcInfoType: types.TypeAlias = types.Union[
|
|
bool,
|
|
types.Tuple[
|
|
types.Type[BaseException],
|
|
BaseException,
|
|
types.Union[types.TracebackType, None],
|
|
],
|
|
types.Tuple[None, None, None],
|
|
BaseException,
|
|
None,
|
|
]
|
|
_P = types.ParamSpec('_P')
|
|
_T = types.TypeVar('_T', covariant=True)
|
|
|
|
|
|
class LoggerProtocol(types.Protocol):
|
|
def debug(
|
|
self,
|
|
msg: object,
|
|
*args: object,
|
|
exc_info: _ExcInfoType = None,
|
|
stack_info: bool = False,
|
|
stacklevel: int = 1,
|
|
extra: types.Union[types.Mapping[str, object], None] = None,
|
|
) -> None: ...
|
|
|
|
def info(
|
|
self,
|
|
msg: object,
|
|
*args: object,
|
|
exc_info: _ExcInfoType = None,
|
|
stack_info: bool = False,
|
|
stacklevel: int = 1,
|
|
extra: types.Union[types.Mapping[str, object], None] = None,
|
|
) -> None: ...
|
|
|
|
def warning(
|
|
self,
|
|
msg: object,
|
|
*args: object,
|
|
exc_info: _ExcInfoType = None,
|
|
stack_info: bool = False,
|
|
stacklevel: int = 1,
|
|
extra: types.Union[types.Mapping[str, object], None] = None,
|
|
) -> None: ...
|
|
|
|
def error(
|
|
self,
|
|
msg: object,
|
|
*args: object,
|
|
exc_info: _ExcInfoType = None,
|
|
stack_info: bool = False,
|
|
stacklevel: int = 1,
|
|
extra: types.Union[types.Mapping[str, object], None] = None,
|
|
) -> None: ...
|
|
|
|
def critical(
|
|
self,
|
|
msg: object,
|
|
*args: object,
|
|
exc_info: _ExcInfoType = None,
|
|
stack_info: bool = False,
|
|
stacklevel: int = 1,
|
|
extra: types.Union[types.Mapping[str, object], None] = None,
|
|
) -> None: ...
|
|
|
|
def exception(
|
|
self,
|
|
msg: object,
|
|
*args: object,
|
|
exc_info: _ExcInfoType = None,
|
|
stack_info: bool = False,
|
|
stacklevel: int = 1,
|
|
extra: types.Union[types.Mapping[str, object], None] = None,
|
|
) -> None: ...
|
|
|
|
def log(
|
|
self,
|
|
level: int,
|
|
msg: object,
|
|
*args: object,
|
|
exc_info: _ExcInfoType = None,
|
|
stack_info: bool = False,
|
|
stacklevel: int = 1,
|
|
extra: types.Union[types.Mapping[str, object], None] = None,
|
|
) -> None: ...
|
|
|
|
|
|
class LoggerBase(abc.ABC):
|
|
"""Class which automatically adds logging utilities to your class when
|
|
interiting. Expects `logger` to be a logging.Logger or compatible instance.
|
|
|
|
Adds easy access to debug, info, warning, error, exception and log methods
|
|
|
|
>>> class MyClass(LoggerBase):
|
|
... logger = logging.getLogger(__name__)
|
|
...
|
|
... def __init__(self):
|
|
... Logged.__init__(self)
|
|
|
|
>>> my_class = MyClass()
|
|
>>> my_class.debug('debug')
|
|
>>> my_class.info('info')
|
|
>>> my_class.warning('warning')
|
|
>>> my_class.error('error')
|
|
>>> my_class.exception('exception')
|
|
>>> my_class.log(0, 'log')
|
|
"""
|
|
|
|
# I've tried using a protocol to properly type the logger but it gave all
|
|
# sorts of issues with mypy so we're using the lazy solution for now. The
|
|
# actual classes define the correct type anyway
|
|
logger: types.Any
|
|
# logger: LoggerProtocol
|
|
|
|
@classmethod
|
|
def __get_name( # pyright: ignore[reportUnusedFunction]
|
|
cls, *name_parts: str
|
|
) -> str:
|
|
return '.'.join(n.strip() for n in name_parts if n.strip())
|
|
|
|
@decorators.wraps_classmethod(logging.Logger.debug)
|
|
@classmethod
|
|
def debug(
|
|
cls,
|
|
msg: object,
|
|
*args: object,
|
|
exc_info: _ExcInfoType = None,
|
|
stack_info: bool = False,
|
|
stacklevel: int = 1,
|
|
extra: types.Union[types.Mapping[str, object], None] = None,
|
|
) -> None:
|
|
return cls.logger.debug( # type: ignore[no-any-return]
|
|
msg,
|
|
*args,
|
|
exc_info=exc_info,
|
|
stack_info=stack_info,
|
|
stacklevel=stacklevel,
|
|
extra=extra,
|
|
)
|
|
|
|
@decorators.wraps_classmethod(logging.Logger.info)
|
|
@classmethod
|
|
def info(
|
|
cls,
|
|
msg: object,
|
|
*args: object,
|
|
exc_info: _ExcInfoType = None,
|
|
stack_info: bool = False,
|
|
stacklevel: int = 1,
|
|
extra: types.Union[types.Mapping[str, object], None] = None,
|
|
) -> None:
|
|
return cls.logger.info( # type: ignore[no-any-return]
|
|
msg,
|
|
*args,
|
|
exc_info=exc_info,
|
|
stack_info=stack_info,
|
|
stacklevel=stacklevel,
|
|
extra=extra,
|
|
)
|
|
|
|
@decorators.wraps_classmethod(logging.Logger.warning)
|
|
@classmethod
|
|
def warning(
|
|
cls,
|
|
msg: object,
|
|
*args: object,
|
|
exc_info: _ExcInfoType = None,
|
|
stack_info: bool = False,
|
|
stacklevel: int = 1,
|
|
extra: types.Union[types.Mapping[str, object], None] = None,
|
|
) -> None:
|
|
return cls.logger.warning( # type: ignore[no-any-return]
|
|
msg,
|
|
*args,
|
|
exc_info=exc_info,
|
|
stack_info=stack_info,
|
|
stacklevel=stacklevel,
|
|
extra=extra,
|
|
)
|
|
|
|
@decorators.wraps_classmethod(logging.Logger.error)
|
|
@classmethod
|
|
def error(
|
|
cls,
|
|
msg: object,
|
|
*args: object,
|
|
exc_info: _ExcInfoType = None,
|
|
stack_info: bool = False,
|
|
stacklevel: int = 1,
|
|
extra: types.Union[types.Mapping[str, object], None] = None,
|
|
) -> None:
|
|
return cls.logger.error( # type: ignore[no-any-return]
|
|
msg,
|
|
*args,
|
|
exc_info=exc_info,
|
|
stack_info=stack_info,
|
|
stacklevel=stacklevel,
|
|
extra=extra,
|
|
)
|
|
|
|
@decorators.wraps_classmethod(logging.Logger.critical)
|
|
@classmethod
|
|
def critical(
|
|
cls,
|
|
msg: object,
|
|
*args: object,
|
|
exc_info: _ExcInfoType = None,
|
|
stack_info: bool = False,
|
|
stacklevel: int = 1,
|
|
extra: types.Union[types.Mapping[str, object], None] = None,
|
|
) -> None:
|
|
return cls.logger.critical( # type: ignore[no-any-return]
|
|
msg,
|
|
*args,
|
|
exc_info=exc_info,
|
|
stack_info=stack_info,
|
|
stacklevel=stacklevel,
|
|
extra=extra,
|
|
)
|
|
|
|
@decorators.wraps_classmethod(logging.Logger.exception)
|
|
@classmethod
|
|
def exception(
|
|
cls,
|
|
msg: object,
|
|
*args: object,
|
|
exc_info: _ExcInfoType = None,
|
|
stack_info: bool = False,
|
|
stacklevel: int = 1,
|
|
extra: types.Union[types.Mapping[str, object], None] = None,
|
|
) -> None:
|
|
return cls.logger.exception( # type: ignore[no-any-return]
|
|
msg,
|
|
*args,
|
|
exc_info=exc_info,
|
|
stack_info=stack_info,
|
|
stacklevel=stacklevel,
|
|
extra=extra,
|
|
)
|
|
|
|
@decorators.wraps_classmethod(logging.Logger.log)
|
|
@classmethod
|
|
def log(
|
|
cls,
|
|
level: int,
|
|
msg: object,
|
|
*args: object,
|
|
exc_info: _ExcInfoType = None,
|
|
stack_info: bool = False,
|
|
stacklevel: int = 1,
|
|
extra: types.Union[types.Mapping[str, object], None] = None,
|
|
) -> None:
|
|
return cls.logger.log( # type: ignore[no-any-return]
|
|
level,
|
|
msg,
|
|
*args,
|
|
exc_info=exc_info,
|
|
stack_info=stack_info,
|
|
stacklevel=stacklevel,
|
|
extra=extra,
|
|
)
|
|
|
|
|
|
class Logged(LoggerBase):
|
|
"""Class which automatically adds a named logger to your class when
|
|
interiting.
|
|
|
|
Adds easy access to debug, info, warning, error, exception and log methods
|
|
|
|
>>> class MyClass(Logged):
|
|
... def __init__(self):
|
|
... Logged.__init__(self)
|
|
|
|
>>> my_class = MyClass()
|
|
>>> my_class.debug('debug')
|
|
>>> my_class.info('info')
|
|
>>> my_class.warning('warning')
|
|
>>> my_class.error('error')
|
|
>>> my_class.exception('exception')
|
|
>>> my_class.log(0, 'log')
|
|
|
|
>>> my_class._Logged__get_name('spam')
|
|
'spam'
|
|
"""
|
|
|
|
logger: logging.Logger # pragma: no cover
|
|
|
|
@classmethod
|
|
def __get_name(cls, *name_parts: str) -> str:
|
|
return types.cast(
|
|
str,
|
|
LoggerBase._LoggerBase__get_name(*name_parts), # type: ignore[attr-defined] # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType, reportAttributeAccessIssue]
|
|
)
|
|
|
|
def __new__(cls, *args: types.Any, **kwargs: types.Any) -> 'Logged':
|
|
"""
|
|
Create a new instance of the class and initialize the logger.
|
|
|
|
The logger is named using the module and class name.
|
|
|
|
Args:
|
|
*args: Variable length argument list.
|
|
**kwargs: Arbitrary keyword arguments.
|
|
|
|
Returns:
|
|
An instance of the class.
|
|
"""
|
|
cls.logger = logging.getLogger(
|
|
cls.__get_name(cls.__module__, cls.__name__)
|
|
)
|
|
return super().__new__(cls)
|