diff --git a/src/delta_barth/constants.py b/src/delta_barth/constants.py index f7e82ff..022295f 100644 --- a/src/delta_barth/constants.py +++ b/src/delta_barth/constants.py @@ -4,6 +4,8 @@ from typing import Final from delta_barth.types import DualDict, HttpContentHeaders +# ** config + # ** lib path lib_path = Path(__file__).parent assert lib_path is not None, "lib path not resolved" @@ -12,6 +14,12 @@ dummy_data_pth = LIB_PATH / "_dummy_data" assert dummy_data_pth.exists(), f"dummy data path not found: {dummy_data_pth}" DUMMY_DATA_PATH: Final[Path] = dummy_data_pth +# ** logging +ENABLE_LOGGING: Final[bool] = True +LOGGING_TO_FILE: Final[bool] = True +LOGGING_TO_STDERR: Final[bool] = True + + # ** error handling DEFAULT_INTERNAL_ERR_CODE: Final[int] = 100 DEFAULT_API_ERR_CODE: Final[int] = 400 diff --git a/src/delta_barth/errors.py b/src/delta_barth/errors.py index cb61bcb..0df1837 100644 --- a/src/delta_barth/errors.py +++ b/src/delta_barth/errors.py @@ -1,11 +1,13 @@ from __future__ import annotations +import traceback import typing as t from collections.abc import Callable from functools import wraps from typing import Any, Final from delta_barth.constants import DEFAULT_API_ERR_CODE, DEFAULT_INTERNAL_ERR_CODE +from delta_barth.logging import logger_wrapped_results as logger from delta_barth.types import DataPipeStates, Status if t.TYPE_CHECKING: @@ -237,6 +239,11 @@ def wrap_result( status = ResultWrapper( result=NotSet(), exception=err, code_on_error=code_on_error ) + fmt_exc = traceback.format_exception(err) + exc_str = "".join(fmt_exc) + logger.error( + "An exception in routine %s occured:\n%s", func.__name__, exc_str + ) return status diff --git a/src/delta_barth/logging.py b/src/delta_barth/logging.py new file mode 100644 index 0000000..0b946fc --- /dev/null +++ b/src/delta_barth/logging.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +import logging +import logging.handlers +from pathlib import Path +from time import gmtime +from typing import Final + +from delta_barth.constants import ENABLE_LOGGING, LIB_PATH, LOGGING_TO_FILE, LOGGING_TO_STDERR + +# ** config +logging.Formatter.converter = gmtime +LOG_FMT: Final[str] = "%(asctime)s | lang_main:%(module)s:%(levelname)s | %(message)s" +LOG_DATE_FMT: Final[str] = "%Y-%m-%d %H:%M:%S +0000" +LOG_FILE_FOLDER: Final[Path] = LIB_PATH / "logs" +if not LOG_FILE_FOLDER.exists(): + LOG_FILE_FOLDER.mkdir(parents=True) + +LOG_FILE_PATH: Final[Path] = LOG_FILE_FOLDER / "lang-main.log" +LOGGING_LEVEL_STDERR: Final[int] = logging.INFO +LOGGING_LEVEL_FILE: Final[int] = logging.DEBUG + +# ** formatters +logger_all_formater = logging.Formatter(fmt=LOG_FMT, datefmt=LOG_DATE_FMT) + +# ** handlers +null_handler = logging.NullHandler() +if ENABLE_LOGGING and LOGGING_TO_STDERR: + logger_all_handler_stderr = logging.StreamHandler() + logger_all_handler_stderr.setLevel(LOGGING_LEVEL_STDERR) + logger_all_handler_stderr.setFormatter(logger_all_formater) +else: # pragma: no cover + logger_all_handler_stderr = null_handler + +if ENABLE_LOGGING and LOGGING_TO_FILE: + logger_all_handler_file = logging.handlers.RotatingFileHandler( + LOG_FILE_PATH, + encoding="utf-8", + maxBytes=5_242_880, + backupCount=1, + ) + logger_all_handler_file.setLevel(LOGGING_LEVEL_FILE) + logger_all_handler_file.setFormatter(logger_all_formater) +else: # pragma: no cover + logger_all_handler_file = null_handler + +# ** loggers and configuration +logger_all = logging.getLogger("delta_barth") +logger_all.addHandler(logger_all_handler_stderr) +logger_all.addHandler(logger_all_handler_file) + +logger_wrapped_results = logging.getLogger("delta_barth.wrapped_results") +logger_wrapped_results.setLevel(logging.DEBUG)