diff --git a/__init__.py b/__init__.py index cc59997..b0079df 100644 --- a/__init__.py +++ b/__init__.py @@ -3,4 +3,4 @@ try: from .src.beans_logging import * except ImportError: - from src.beans_logging import * + from src.beans_logging import * # type: ignore diff --git a/auto.py b/auto.py index e5d08d7..c84f5aa 100644 --- a/auto.py +++ b/auto.py @@ -3,4 +3,4 @@ try: from .src.beans_logging.auto import * except ImportError: - from src.beans_logging.auto import * + from src.beans_logging.auto import * # type: ignore diff --git a/src/beans_logging/__init__.py b/src/beans_logging/__init__.py index afbadff..4246235 100644 --- a/src/beans_logging/__init__.py +++ b/src/beans_logging/__init__.py @@ -1,12 +1,16 @@ from __future__ import annotations from .__version__ import __version__ -from .config import LoggerConfigPM +from .schemas import LoguruHandlerPM, LogHandlerPM +from .config import get_default_handlers, LoggerConfigPM from ._core import Logger, logger, LoggerLoader __all__ = [ "__version__", + "LoguruHandlerPM", + "LogHandlerPM", + "get_default_handlers", "LoggerConfigPM", "Logger", "logger", diff --git a/src/beans_logging/_builder.py b/src/beans_logging/_builder.py index 97a829c..991a9b8 100644 --- a/src/beans_logging/_builder.py +++ b/src/beans_logging/_builder.py @@ -4,7 +4,7 @@ from pydantic import validate_call -from ._constants import LogHandlerTypeEnum, LogLevelEnum +from .constants import LogHandlerTypeEnum, LogLevelEnum from .schemas import LogHandlerPM from .config import LoggerConfigPM from .sinks import std_sink @@ -35,17 +35,15 @@ def build_handler(handler: LogHandlerPM, config: LoggerConfigPM) -> dict[str, An dict[str, Any]: Loguru handler config as dictionary. """ - _handler_dict = handler.model_dump(by_alias=True, exclude_none=True) - - if _handler_dict.get("sink") is None: - if _handler_dict.get("type") == LogHandlerTypeEnum.STD: - _handler_dict["sink"] = std_sink + if handler.sink is None: + if handler.h_type == LogHandlerTypeEnum.STD: + handler.sink = std_sink else: raise ValueError( "'sink' attribute is empty, required for any log handler except std handler!" ) - _sink = _handler_dict.get("sink") + _sink = handler.sink if isinstance(_sink, (str, Path)): if not os.path.isabs(_sink): _sink = os.path.join(config.default.file.logs_dir, _sink) @@ -56,72 +54,75 @@ def build_handler(handler: LogHandlerPM, config: LoggerConfigPM) -> dict[str, An if "{app_name}" in _sink: _sink = _sink.format(app_name=config.app_name) - _handler_dict["sink"] = _sink + handler.sink = _sink - if _handler_dict.get("level") is None: - if _handler_dict.get("error"): - _handler_dict["level"] = config.default.level.err + if handler.level is None: + if handler.error: + handler.level = config.default.level.err else: - _handler_dict["level"] = config.default.level.base + handler.level = config.default.level.base - if (_handler_dict.get("custom_serialize") is None) and _handler_dict.get( - "serialize" - ): - _handler_dict["custom_serialize"] = config.default.custom_serialize - - if _handler_dict.get("custom_serialize"): - _handler_dict["serialize"] = False - _handler_dict["format"] = json_formatter - - if (_handler_dict.get("format") is None) and (not _handler_dict.get("serialize")): - _handler_dict["format"] = config.default.format_str - - if _handler_dict.get("filter") is None: - if _handler_dict.get("type") == LogHandlerTypeEnum.STD: - _handler_dict["filter"] = use_std_filter - elif _handler_dict.get("type") == LogHandlerTypeEnum.FILE: - if _handler_dict.get("serialize") or _handler_dict.get("custom_serialize"): - if _handler_dict.get("error"): - _handler_dict["filter"] = use_file_json_err_filter + if (handler.custom_serialize is None) and handler.serialize: + handler.custom_serialize = config.default.custom_serialize + + if handler.custom_serialize: + handler.serialize = False + handler.format_ = json_formatter + + if (handler.format_ is None) and (not handler.serialize): + handler.format_ = config.default.format_str + + if handler.filter_ is None: + if handler.h_type == LogHandlerTypeEnum.STD: + handler.filter_ = use_std_filter + elif handler.h_type == LogHandlerTypeEnum.FILE: + if handler.serialize or handler.custom_serialize: + if handler.error: + handler.filter_ = use_file_json_err_filter else: - _handler_dict["filter"] = use_file_json_filter + handler.filter_ = use_file_json_filter else: - if _handler_dict.get("error"): - _handler_dict["filter"] = use_file_err_filter + if handler.error: + handler.filter_ = use_file_err_filter else: - _handler_dict["filter"] = use_file_filter + handler.filter_ = use_file_filter else: - _handler_dict["filter"] = use_all_filter + handler.filter_ = use_all_filter - if _handler_dict.get("backtrace") is None: - _handler_dict["backtrace"] = True + if handler.backtrace is None: + handler.backtrace = True - if (_handler_dict.get("diagnose") is None) and ( - (_handler_dict.get("level") == LogLevelEnum.TRACE) - or (_handler_dict.get("level") == 5) + if (handler.diagnose is None) and ( + (handler.level == LogLevelEnum.TRACE) or (handler.level == 5) ): - _handler_dict["diagnose"] = True + handler.diagnose = True - if _handler_dict.get("type") == LogHandlerTypeEnum.FILE: - if _handler_dict.get("enqueue") is None: - _handler_dict["enqueue"] = True + if handler.h_type == LogHandlerTypeEnum.FILE: + if handler.enqueue is None: + handler.enqueue = True - if _handler_dict.get("rotation") is None: - _handler_dict["rotation"] = Rotator( + if handler.rotation is None: + handler.rotation = Rotator( rotate_size=config.default.file.rotate_size, rotate_time=config.default.file.rotate_time, ).should_rotate - if _handler_dict.get("retention") is None: - _handler_dict["retention"] = config.default.file.retention - - if _handler_dict.get("encoding") is None: - _handler_dict["encoding"] = config.default.file.encoding - - _handler_dict.pop("type", None) - _handler_dict.pop("error", None) - _handler_dict.pop("custom_serialize", None) - _handler_dict.pop("enabled", None) + if handler.retention is None: + handler.retention = config.default.file.retention + + if handler.encoding is None: + handler.encoding = config.default.file.encoding + + _handler_dict = handler.model_dump( + by_alias=True, + exclude_none=True, + exclude={ + "enabled", + "h_type", + "error", + "custom_serialize", + }, + ) return _handler_dict diff --git a/src/beans_logging/_constants.py b/src/beans_logging/_constants.py deleted file mode 100644 index 4f50a2c..0000000 --- a/src/beans_logging/_constants.py +++ /dev/null @@ -1,30 +0,0 @@ -from enum import Enum - - -class LogHandlerTypeEnum(str, Enum): - STD = "STD" - FILE = "FILE" - SOCKET = "SOCKET" - HTTP = "HTTP" - SYSLOG = "SYSLOG" - QUEUE = "QUEUE" - MEMORY = "MEMORY" - NULL = "NULL" - CUSTOM = "CUSTOM" - UNKNOWN = "UNKNOWN" - - -class LogLevelEnum(str, Enum): - TRACE = "TRACE" - DEBUG = "DEBUG" - INFO = "INFO" - SUCCESS = "SUCCESS" - WARNING = "WARNING" - ERROR = "ERROR" - CRITICAL = "CRITICAL" - - -__all__ = [ - "LogHandlerTypeEnum", - "LogLevelEnum", -] diff --git a/src/beans_logging/_core.py b/src/beans_logging/_core.py index b4e6303..d5120bc 100644 --- a/src/beans_logging/_core.py +++ b/src/beans_logging/_core.py @@ -17,10 +17,11 @@ from potato_util import io as io_utils # Internal modules +from .constants import DEFAULT_LOGURU_HANDLER_NAME, DEFAULT_NO_HANDLER_NAME_PREFIX from .schemas import LogHandlerPM, LoguruHandlerPM from .config import LoggerConfigPM from ._builder import build_handler -from ._intercept import init_intercepter +from .intercepters import add_intercepter class LoggerLoader: @@ -52,7 +53,7 @@ def __init__( **kwargs, ) -> None: - self.handlers_map = {"default.loguru_handler": 0} + self.handlers_map = {DEFAULT_LOGURU_HANDLER_NAME: 0} if not config: config = LoggerConfigPM() @@ -63,15 +64,15 @@ def __init__( self.config_path = config_path if auto_load: - self.load() + self.load(load_config_file=True) @validate_call - def load(self, load_config_file: bool = True) -> "Logger": + def load(self, load_config_file: bool = False) -> "Logger": """Load logger handlers based on logger config. Args: load_config_file (bool, optional): Whether to load config from file before loading handlers. - Default is True. + Default is False. Returns: Logger: Main loguru logger instance. @@ -84,7 +85,7 @@ def load(self, load_config_file: bool = True) -> "Logger": for _key, _handler in self.config.handlers.items(): self.add_handler(name=_key, handler=_handler) - init_intercepter(config=self.config) + add_intercepter(config=self.config) return logger def _load_config_file(self) -> None: @@ -195,7 +196,7 @@ def add_handler( _handler_id = logger.add(**_handler_dict) if not name: - name = f"log_handler.{uuid.uuid4().hex}" + name = f"{DEFAULT_NO_HANDLER_NAME_PREFIX}{uuid.uuid4().hex}" self.handlers_map[name] = _handler_id diff --git a/src/beans_logging/config.py b/src/beans_logging/config.py index 412b83d..2e708a4 100644 --- a/src/beans_logging/config.py +++ b/src/beans_logging/config.py @@ -5,11 +5,19 @@ import potato_util as utils from pydantic import Field, field_validator -from ._constants import LogHandlerTypeEnum, LogLevelEnum -from .schemas import ExtraBaseModel, LogHandlerPM, LoguruHandlerPM - - -def _get_handlers() -> dict[str, LogHandlerPM]: +from .constants import ( + LogLevelEnum, + LogHandlerTypeEnum, + DEFAULT_ALL_STD_HANDLER_NAME, + DEFAULT_ALL_FILE_HANDLER_NAME, + DEFAULT_ERR_FILE_HANDLER_NAME, + DEFAULT_ALL_JSON_HANDLER_NAME, + DEFAULT_ERR_JSON_HANDLER_NAME, +) +from .schemas import ExtraBaseModel, LogHandlerPM + + +def get_default_handlers() -> dict[str, LogHandlerPM]: """Get default log handlers. Returns: @@ -17,37 +25,37 @@ def _get_handlers() -> dict[str, LogHandlerPM]: """ _log_handlers: dict[str, LogHandlerPM] = { - "default.all.std_handler": LogHandlerPM( - type_=LogHandlerTypeEnum.STD, + DEFAULT_ALL_STD_HANDLER_NAME: LogHandlerPM( + h_type=LogHandlerTypeEnum.STD, format_=( "[{time:YYYY-MM-DD HH:mm:ss.SSS Z} | {extra[level_short]:<5} | " "{name}:{line}]: {message}" ), colorize=True, ), - "default.all.file_handler": LogHandlerPM( - type_=LogHandlerTypeEnum.FILE, - sink="{app_name}.all.log", + DEFAULT_ALL_FILE_HANDLER_NAME: LogHandlerPM( enabled=False, + h_type=LogHandlerTypeEnum.FILE, + sink="{app_name}.all.log", ), - "default.err.file_handler": LogHandlerPM( - type_=LogHandlerTypeEnum.FILE, + DEFAULT_ERR_FILE_HANDLER_NAME: LogHandlerPM( + enabled=False, + h_type=LogHandlerTypeEnum.FILE, sink="{app_name}.err.log", error=True, - enabled=False, ), - "default.all.json_handler": LogHandlerPM( - type_=LogHandlerTypeEnum.FILE, - sink="json/{app_name}.json.all.log", - serialize=True, + DEFAULT_ALL_JSON_HANDLER_NAME: LogHandlerPM( enabled=False, + h_type=LogHandlerTypeEnum.FILE, + sink="json/{app_name}.all.json.log", + serialize=True, ), - "default.err.json_handler": LogHandlerPM( - type_=LogHandlerTypeEnum.FILE, - sink="json/{app_name}.json.err.log", + DEFAULT_ERR_JSON_HANDLER_NAME: LogHandlerPM( + enabled=False, + h_type=LogHandlerTypeEnum.FILE, + sink="json/{app_name}.err.json.log", serialize=True, error=True, - enabled=False, ), } @@ -131,36 +139,53 @@ class LoggerConfigPM(ExtraBaseModel): ) default: DefaultConfigPM = Field(default_factory=DefaultConfigPM) intercept: InterceptConfigPM = Field(default_factory=InterceptConfigPM) - handlers: dict[str, LogHandlerPM] = Field(default_factory=_get_handlers) + handlers: dict[str, LogHandlerPM] = Field(default_factory=get_default_handlers) extra: ExtraConfigPM | None = Field(default_factory=ExtraConfigPM) @field_validator("handlers", mode="before") @classmethod - def _check_handlers(cls, val: Any) -> Any: - if val: - if not isinstance(val, dict): + def _check_handlers(cls, val: Any) -> dict[str, LogHandlerPM]: + + _default_handlers = get_default_handlers() + + if not val: + val = _default_handlers + return val + + if not isinstance(val, dict): + raise TypeError( + f"'handlers' attribute type {type(val).__name__} is invalid, must be a dict of or dict!" + ) + + for _key, _handler in val.items(): + if not isinstance(_handler, (LogHandlerPM, dict)): raise TypeError( - f"'handlers' attribute type {type(val).__name__} is invalid, must be a dict of , " - f" or dict!" + f"'handlers' attribute's '{_key}' key -> value type {type(_handler).__name__} is invalid, must be " + f" or dict!" + ) + + if isinstance(_handler, LogHandlerPM): + val[_key] = _handler.model_dump( + by_alias=True, exclude_unset=True, exclude_none=True ) - for _i, _handler in val.items(): - if not isinstance(_handler, (LogHandlerPM, LoguruHandlerPM, dict)): - raise TypeError( - f"'handlers' attribute index {_i} type {type(_handler).__name__} is invalid, must be " - f", or dict!" - ) + _default_dict = { + _key: _handler.model_dump( + by_alias=True, exclude_unset=True, exclude_none=True + ) + for _key, _handler in _default_handlers.items() + } + + if _default_dict != val: + val = utils.deep_merge(_default_dict, val) - if isinstance(_handler, LoguruHandlerPM): - val[_i] = LogHandlerPM( - **_handler.model_dump(exclude_none=True, exclude_unset=True) - ) - elif isinstance(_handler, dict): - val[_i] = LogHandlerPM(**_handler) + for _key, _handler in val.items(): + val[_key] = LogHandlerPM(**_handler) return val __all__ = [ "LoggerConfigPM", + "get_default_handlers", ] diff --git a/src/beans_logging/constants.py b/src/beans_logging/constants.py new file mode 100644 index 0000000..2105d83 --- /dev/null +++ b/src/beans_logging/constants.py @@ -0,0 +1,46 @@ +from enum import Enum + + +class LogHandlerTypeEnum(str, Enum): + STD = "STD" + FILE = "FILE" + SOCKET = "SOCKET" + HTTP = "HTTP" + SYSLOG = "SYSLOG" + QUEUE = "QUEUE" + MEMORY = "MEMORY" + NULL = "NULL" + CUSTOM = "CUSTOM" + UNKNOWN = "UNKNOWN" + + +class LogLevelEnum(str, Enum): + TRACE = "TRACE" + DEBUG = "DEBUG" + INFO = "INFO" + SUCCESS = "SUCCESS" + WARNING = "WARNING" + ERROR = "ERROR" + CRITICAL = "CRITICAL" + + +DEFAULT_LOGURU_HANDLER_NAME = "default.loguru.handler" +DEFAULT_ALL_STD_HANDLER_NAME = "default.all.std_handler" +DEFAULT_ALL_FILE_HANDLER_NAME = "default.all.file_handler" +DEFAULT_ERR_FILE_HANDLER_NAME = "default.err.file_handler" +DEFAULT_ALL_JSON_HANDLER_NAME = "default.all.json_handler" +DEFAULT_ERR_JSON_HANDLER_NAME = "default.err.json_handler" +DEFAULT_NO_HANDLER_NAME_PREFIX = "log_handler." + + +__all__ = [ + "LogHandlerTypeEnum", + "LogLevelEnum", + "DEFAULT_LOGURU_HANDLER_NAME", + "DEFAULT_ALL_STD_HANDLER_NAME", + "DEFAULT_ALL_FILE_HANDLER_NAME", + "DEFAULT_ERR_FILE_HANDLER_NAME", + "DEFAULT_ALL_JSON_HANDLER_NAME", + "DEFAULT_ERR_JSON_HANDLER_NAME", + "DEFAULT_NO_HANDLER_NAME_PREFIX", +] diff --git a/src/beans_logging/_intercept.py b/src/beans_logging/intercepters.py similarity index 97% rename from src/beans_logging/_intercept.py rename to src/beans_logging/intercepters.py index c48a12c..65044d1 100644 --- a/src/beans_logging/_intercept.py +++ b/src/beans_logging/intercepters.py @@ -45,7 +45,7 @@ def emit(self, record: LogRecord) -> None: @validate_call -def init_intercepter(config: LoggerConfigPM) -> None: +def add_intercepter(config: LoggerConfigPM) -> None: """Initialize log interceptor based on provided config. Args: @@ -102,5 +102,5 @@ def init_intercepter(config: LoggerConfigPM) -> None: __all__ = [ "InterceptHandler", - "init_intercepter", + "add_intercepter", ] diff --git a/src/beans_logging/schemas.py b/src/beans_logging/schemas.py index 23be9b6..9d112ab 100644 --- a/src/beans_logging/schemas.py +++ b/src/beans_logging/schemas.py @@ -19,7 +19,7 @@ from loguru import Record, Message from pydantic import BaseModel, Field, ConfigDict, model_validator -from ._constants import LogHandlerTypeEnum, LogLevelEnum +from .constants import LogHandlerTypeEnum, LogLevelEnum class ExtraBaseModel(BaseModel): @@ -94,11 +94,7 @@ class LoguruHandlerPM(ExtraBaseModel): class LogHandlerPM(LoguruHandlerPM): - type_: LogHandlerTypeEnum = Field( - default=LogHandlerTypeEnum.UNKNOWN, - validation_alias="type", - serialization_alias="type", - ) + h_type: LogHandlerTypeEnum = Field(default=LogHandlerTypeEnum.UNKNOWN) sink: _SinkType | None = Field(default=None) level: str | int | LogLevelEnum | None = Field(default=None) custom_serialize: bool | None = Field(default=None) diff --git a/templates/configs/logger.yml b/templates/configs/logger.yml index 2c66041..f2772c8 100644 --- a/templates/configs/logger.yml +++ b/templates/configs/logger.yml @@ -20,28 +20,28 @@ logger: mute_modules: [] handlers: default.all.std_handler: - type: STD + enabled: true + h_type: STD format: "[{time:YYYY-MM-DD HH:mm:ss.SSS Z} | {extra[level_short]:<5} | {name}:{line}]: {message}" colorize: true - enabled: true default.all.file_handler: - type: FILE - sink: "{app_name}.all.log" enabled: false + h_type: FILE + sink: "{app_name}.all.log" default.err.file_handler: - type: FILE + enabled: false + h_type: FILE sink: "{app_name}.err.log" error: true - enabled: false default.all.json_handler: - type: FILE - sink: "json/{app_name}.json.all.log" - serialize: true enabled: false + h_type: FILE + sink: "json/{app_name}.all.json.log" + serialize: true default.err.json_handler: - type: FILE - sink: "json/{app_name}.json.err.log" + enabled: false + h_type: FILE + sink: "json/{app_name}.err.json.log" serialize: true error: true - enabled: false extra: