From 9c6078f4fd08e15d55d8688dc95ce977c9b41905 Mon Sep 17 00:00:00 2001 From: edraft Date: Wed, 17 Sep 2025 22:18:38 +0200 Subject: [PATCH] with_logging & logger level fix --- .../cpl/application/abc/application_abc.py | 9 ++++ .../cpl/core/environment/environment.py | 8 +-- src/cpl-core/cpl/core/log/__init__.py | 2 +- src/cpl-core/cpl/core/log/log_level_enum.py | 2 +- src/cpl-core/cpl/core/log/logger.py | 53 +++++++++++-------- src/cpl-core/cpl/core/log/logger_abc.py | 3 +- src/cpl-core/cpl/core/log/logging_settings.py | 18 +++---- src/cpl-core/cpl/core/utils/get_value.py | 14 +++++ tests/custom/database/src/application.py | 2 +- tests/custom/database/src/main.py | 1 + tests/custom/database/src/main_simplified.py | 4 ++ 11 files changed, 78 insertions(+), 38 deletions(-) diff --git a/src/cpl-application/cpl/application/abc/application_abc.py b/src/cpl-application/cpl/application/abc/application_abc.py index 4199eb1d..f48b1387 100644 --- a/src/cpl-application/cpl/application/abc/application_abc.py +++ b/src/cpl-application/cpl/application/abc/application_abc.py @@ -3,6 +3,8 @@ from typing import Callable, Self from cpl.application.host import Host from cpl.core.console.console import Console +from cpl.core.environment import Environment +from cpl.core.log import LoggerABC, LogLevel from cpl.dependency.service_provider_abc import ServiceProviderABC @@ -38,6 +40,13 @@ class ApplicationABC(ABC): setattr(cls, name, func) return cls + def with_logging(self, level: LogLevel = None): + if level is None: + level = Environment.get("LOG_LEVEL", LogLevel, LogLevel.info) + + logger = self._services.get_service(LoggerABC) + logger.set_level(level) + def with_permissions(self, *args, **kwargs): __not_implemented__("cpl-auth", self.with_permissions) diff --git a/src/cpl-core/cpl/core/environment/environment.py b/src/cpl-core/cpl/core/environment/environment.py index 15576543..566ed5e8 100644 --- a/src/cpl-core/cpl/core/environment/environment.py +++ b/src/cpl-core/cpl/core/environment/environment.py @@ -3,7 +3,7 @@ from socket import gethostname from typing import Optional, Type from cpl.core.environment.environment_enum import EnvironmentEnum -from cpl.core.typing import T +from cpl.core.typing import T, D from cpl.core.utils.get_value import get_value @@ -55,14 +55,14 @@ class Environment: os.environ[key] = str(value) @staticmethod - def get(key: str, cast_type: Type[T], default: Optional[T] = None) -> Optional[T]: + def get(key: str, cast_type: Type[T], default: D = None) -> T | D: """ Get an environment variable and cast it to a specified type. :param str key: The name of the environment variable. :param Type[T] cast_type: A callable to cast the variable's value. - :param Optional[T] default: The default value to return if the variable is not found. Defaults to None.The default value to return if the variable is not found. Defaults to None. + :param T default: The default value to return if the variable is not found. Defaults to None.The default value to return if the variable is not found. Defaults to None. :return: The casted value, or None if the variable is not found. - :rtype: Optional[T] + :rtype: T | D """ return get_value(dict(os.environ), key, cast_type, default) diff --git a/src/cpl-core/cpl/core/log/__init__.py b/src/cpl-core/cpl/core/log/__init__.py index 7a58a84e..496346ff 100644 --- a/src/cpl-core/cpl/core/log/__init__.py +++ b/src/cpl-core/cpl/core/log/__init__.py @@ -1,4 +1,4 @@ from .logger import Logger from .logger_abc import LoggerABC -from .log_level_enum import LogLevelEnum +from .log_level_enum import LogLevel from .logging_settings import LogSettings diff --git a/src/cpl-core/cpl/core/log/log_level_enum.py b/src/cpl-core/cpl/core/log/log_level_enum.py index daa15818..d9bd1fa9 100644 --- a/src/cpl-core/cpl/core/log/log_level_enum.py +++ b/src/cpl-core/cpl/core/log/log_level_enum.py @@ -1,7 +1,7 @@ from enum import Enum -class LogLevelEnum(Enum): +class LogLevel(Enum): off = "OFF" # Nothing trace = "TRC" # Detailed app information's debug = "DEB" # Detailed app state diff --git a/src/cpl-core/cpl/core/log/logger.py b/src/cpl-core/cpl/core/log/logger.py index 42e6455b..9f413691 100644 --- a/src/cpl-core/cpl/core/log/logger.py +++ b/src/cpl-core/cpl/core/log/logger.py @@ -3,28 +3,31 @@ import traceback from datetime import datetime from cpl.core.console import Console -from cpl.core.log.log_level_enum import LogLevelEnum +from cpl.core.log.log_level_enum import LogLevel from cpl.core.log.logger_abc import LoggerABC from cpl.core.typing import Messages, Source class Logger(LoggerABC): - _level = LogLevelEnum.info - _levels = [x for x in LogLevelEnum] + _level = LogLevel.info + _levels = [x for x in LogLevel] # ANSI color codes for different log levels _COLORS = { - LogLevelEnum.trace: "\033[37m", # Light Gray - LogLevelEnum.debug: "\033[94m", # Blue - LogLevelEnum.info: "\033[92m", # Green - LogLevelEnum.warning: "\033[93m", # Yellow - LogLevelEnum.error: "\033[91m", # Red - LogLevelEnum.fatal: "\033[95m", # Magenta + LogLevel.trace: "\033[37m", # Light Gray + LogLevel.debug: "\033[94m", # Blue + LogLevel.info: "\033[92m", # Green + LogLevel.warning: "\033[93m", # Yellow + LogLevel.error: "\033[91m", # Red + LogLevel.fatal: "\033[95m", # Magenta } def __init__(self, source: Source, file_prefix: str = None): LoggerABC.__init__(self) - assert source is not None and source != "", "Source cannot be None or empty" + + if source == LoggerABC.__name__: + source = None + self._source = source if file_prefix is None: @@ -45,7 +48,7 @@ class Logger(LoggerABC): os.makedirs("logs") @classmethod - def set_level(cls, level: LogLevelEnum): + def set_level(cls, level: LogLevel): if level in cls._levels: cls._level = level else: @@ -69,7 +72,7 @@ class Logger(LoggerABC): log_file.write(content + "\n") log_file.close() - def _log(self, level: LogLevelEnum, *messages: Messages): + def _log(self, level: LogLevel, *messages: Messages): try: if self._levels.index(level) < self._levels.index(self._level): return @@ -78,7 +81,7 @@ class Logger(LoggerABC): formatted_message = self._format_message(level.value, timestamp, *messages) self._write_log_to_file(formatted_message) - Console.write_line(f"{self._COLORS.get(self._level, '\033[0m')}{formatted_message}\033[0m") + Console.write_line(f"{self._COLORS.get(level, '\033[0m')}{formatted_message}\033[0m") except Exception as e: print(f"Error while logging: {e} -> {traceback.format_exc()}") @@ -91,27 +94,35 @@ class Logger(LoggerABC): messages = [str(message) for message in messages if message is not None] - return f"<{timestamp}> [{level.upper():^3}] [{self._file_prefix}] - [{self._source}]: {' '.join(messages)}" + message = f"<{timestamp}>" + message += f" [{level.upper():^3}]" + message += f" [{self._file_prefix}]" + if self._source is not None: + message += f" - [{self._source}]" + + message += f": {' '.join(messages)}" + + return message def header(self, string: str): - self._log(LogLevelEnum.info, string) + self._log(LogLevel.info, string) def trace(self, *messages: Messages): - self._log(LogLevelEnum.trace, *messages) + self._log(LogLevel.trace, *messages) def debug(self, *messages: Messages): - self._log(LogLevelEnum.debug, *messages) + self._log(LogLevel.debug, *messages) def info(self, *messages: Messages): - self._log(LogLevelEnum.info, *messages) + self._log(LogLevel.info, *messages) def warning(self, *messages: Messages): - self._log(LogLevelEnum.warning, *messages) + self._log(LogLevel.warning, *messages) def error(self, message, e: Exception = None): - self._log(LogLevelEnum.error, message, f"{e} -> {traceback.format_exc()}" if e else None) + self._log(LogLevel.error, message, f"{e} -> {traceback.format_exc()}" if e else None) def fatal(self, message, e: Exception = None, prevent_quit: bool = False): - self._log(LogLevelEnum.fatal, message, f"{e} -> {traceback.format_exc()}" if e else None) + self._log(LogLevel.fatal, message, f"{e} -> {traceback.format_exc()}" if e else None) if not prevent_quit: exit(-1) diff --git a/src/cpl-core/cpl/core/log/logger_abc.py b/src/cpl-core/cpl/core/log/logger_abc.py index d0cc0343..08a7f00e 100644 --- a/src/cpl-core/cpl/core/log/logger_abc.py +++ b/src/cpl-core/cpl/core/log/logger_abc.py @@ -1,5 +1,6 @@ from abc import abstractmethod, ABC +from cpl.core.log.log_level_enum import LogLevel from cpl.core.typing import Messages @@ -7,7 +8,7 @@ class LoggerABC(ABC): r"""ABC for :class:`cpl.core.log.logger_service.Logger`""" @abstractmethod - def set_level(self, level: str): ... + def set_level(self, level: LogLevel): ... @abstractmethod def _format_message(self, level: str, timestamp, *messages: Messages) -> str: ... diff --git a/src/cpl-core/cpl/core/log/logging_settings.py b/src/cpl-core/cpl/core/log/logging_settings.py index 31afa876..6055bf1c 100644 --- a/src/cpl-core/cpl/core/log/logging_settings.py +++ b/src/cpl-core/cpl/core/log/logging_settings.py @@ -1,7 +1,7 @@ from typing import Optional from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC -from cpl.core.log.log_level_enum import LogLevelEnum +from cpl.core.log.log_level_enum import LogLevel class LogSettings(ConfigurationModelABC): @@ -11,14 +11,14 @@ class LogSettings(ConfigurationModelABC): self, path: str = None, filename: str = None, - console_log_level: LogLevelEnum = None, - file_log_level: LogLevelEnum = None, + console_log_level: LogLevel = None, + file_log_level: LogLevel = None, ): ConfigurationModelABC.__init__(self) self._path: Optional[str] = path self._filename: Optional[str] = filename - self._console: Optional[LogLevelEnum] = console_log_level - self._level: Optional[LogLevelEnum] = file_log_level + self._console: Optional[LogLevel] = console_log_level + self._level: Optional[LogLevel] = file_log_level @property def path(self) -> str: @@ -37,17 +37,17 @@ class LogSettings(ConfigurationModelABC): self._filename = filename @property - def console(self) -> LogLevelEnum: + def console(self) -> LogLevel: return self._console @console.setter - def console(self, console: LogLevelEnum) -> None: + def console(self, console: LogLevel) -> None: self._console = console @property - def level(self) -> LogLevelEnum: + def level(self) -> LogLevel: return self._level @level.setter - def level(self, level: LogLevelEnum) -> None: + def level(self, level: LogLevel) -> None: self._level = level diff --git a/src/cpl-core/cpl/core/utils/get_value.py b/src/cpl-core/cpl/core/utils/get_value.py index 3a3cf4c3..a5419349 100644 --- a/src/cpl-core/cpl/core/utils/get_value.py +++ b/src/cpl-core/cpl/core/utils/get_value.py @@ -1,3 +1,4 @@ +from enum import Enum from typing import Type, Optional from cpl.core.typing import T @@ -40,6 +41,19 @@ def get_value( if cast_type == bool: return value.lower() in ["true", "1"] + if issubclass(cast_type, Enum): + try: + return cast_type(value) + except ValueError: + pass + + try: + return cast_type[value] + except KeyError: + pass + + return default + if (cast_type if not hasattr(cast_type, "__origin__") else cast_type.__origin__) == list: if not (value.startswith("[") and value.endswith("]")) and list_delimiter not in value: raise ValueError("List values must be enclosed in square brackets or use a delimiter.") diff --git a/tests/custom/database/src/application.py b/tests/custom/database/src/application.py index 0a1190a0..5612859f 100644 --- a/tests/custom/database/src/application.py +++ b/tests/custom/database/src/application.py @@ -14,7 +14,7 @@ class Application(ApplicationABC): def __init__(self, services: ServiceProviderABC): ApplicationABC.__init__(self, services) - self._logger: LoggerABC = services.get_service(LoggerABC) + self._logger = services.get_service(LoggerABC) async def test_daos(self): userDao: UserDao = self._services.get_service(UserDao) diff --git a/tests/custom/database/src/main.py b/tests/custom/database/src/main.py index 78e2c659..cb3eb749 100644 --- a/tests/custom/database/src/main.py +++ b/tests/custom/database/src/main.py @@ -8,6 +8,7 @@ def main(): builder = ApplicationBuilder(Application).with_startup(Startup) app = builder.build() + app.with_logging() app.with_permissions(CustomPermissions) app.with_migrations("./scripts") app.with_seeders() diff --git a/tests/custom/database/src/main_simplified.py b/tests/custom/database/src/main_simplified.py index 3fdadeb1..8b1568ba 100644 --- a/tests/custom/database/src/main_simplified.py +++ b/tests/custom/database/src/main_simplified.py @@ -2,14 +2,18 @@ from application import Application from cpl.application import ApplicationBuilder from cpl.auth.permission.permissions_registry import PermissionsRegistry from cpl.core.console import Console +from cpl.core.log import LogLevel from custom_permissions import CustomPermissions from startup import Startup def main(): builder = ApplicationBuilder(Application).with_startup(Startup) + builder.services.add_logging() + app = builder.build() + app.with_logging(LogLevel.trace) app.with_permissions(CustomPermissions) app.with_migrations("./scripts") app.with_seeders()