Console & file logging format msg seperate & removed timestamp from console & minor fixes to di
All checks were successful
Test before pr merge / test-lint (pull_request) Successful in 7s
Build on push / prepare (push) Successful in 10s
Build on push / core (push) Successful in 18s
Build on push / query (push) Successful in 19s
Build on push / dependency (push) Successful in 14s
Build on push / translation (push) Successful in 16s
Build on push / mail (push) Successful in 17s
Build on push / application (push) Successful in 19s
Build on push / database (push) Successful in 19s
Build on push / auth (push) Successful in 25s
Build on push / api (push) Successful in 14s

This commit is contained in:
2025-09-26 15:47:56 +02:00
parent c410a692be
commit e0f6e1c241
11 changed files with 93 additions and 41 deletions

View File

@@ -7,6 +7,7 @@ from cpl.core.environment import Environment
from cpl.core.log import LoggerABC from cpl.core.log import LoggerABC
from cpl.core.pipes import IPAddressPipe from cpl.core.pipes import IPAddressPipe
from cpl.dependency import ServiceProvider from cpl.dependency import ServiceProvider
from cpl.dependency.typing import Modules
from cpl.mail import EMail, EMailClientABC from cpl.mail import EMail, EMailClientABC
from cpl.query import List from cpl.query import List
from scoped_service import ScopedService from scoped_service import ScopedService
@@ -16,8 +17,8 @@ from test_settings import TestSettings
class Application(ApplicationABC): class Application(ApplicationABC):
def __init__(self, services: ServiceProvider): def __init__(self, services: ServiceProvider, modules: Modules):
ApplicationABC.__init__(self, services) ApplicationABC.__init__(self, services, modules)
self._logger = self._services.get_service(LoggerABC) self._logger = self._services.get_service(LoggerABC)
self._mailer = self._services.get_service(EMailClientABC) self._mailer = self._services.get_service(EMailClientABC)

View File

@@ -114,6 +114,9 @@ class ApplicationABC(ABC):
Host.run_app(self.main) Host.run_app(self.main)
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
finally:
logger = self._services.get_service(LoggerABC)
logger.info("Application shutdown")
@abstractmethod @abstractmethod
def main(self): ... def main(self): ...

View File

@@ -6,7 +6,6 @@ from cpl.application.abc.application_extension_abc import ApplicationExtensionAB
from cpl.application.abc.startup_abc import StartupABC from cpl.application.abc.startup_abc import StartupABC
from cpl.application.abc.startup_extension_abc import StartupExtensionABC from cpl.application.abc.startup_extension_abc import StartupExtensionABC
from cpl.application.host import Host from cpl.application.host import Host
from cpl.core.errors import dependency_error
from cpl.dependency.context import get_provider, use_root_provider from cpl.dependency.context import get_provider, use_root_provider
from cpl.dependency.service_collection import ServiceCollection from cpl.dependency.service_collection import ServiceCollection

View File

@@ -93,14 +93,13 @@ class Logger(LoggerABC):
def _log(self, level: LogLevel, *messages: Messages): def _log(self, level: LogLevel, *messages: Messages):
try: try:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
formatted_message = self._format_message(level.value, timestamp, *messages)
self._write_log_to_file(level, formatted_message) self._write_log_to_file(level, self._file_format_message(level.value, timestamp, *messages))
self._write_to_console(level, formatted_message) self._write_to_console(level, self._console_format_message(level.value, timestamp, *messages))
except Exception as e: except Exception as e:
print(f"Error while logging: {e} -> {traceback.format_exc()}") print(f"Error while logging: {e} -> {traceback.format_exc()}")
def _format_message(self, level: str, timestamp, *messages: Messages) -> str: def _file_format_message(self, level: str, timestamp, *messages: Messages) -> str:
if isinstance(messages, tuple): if isinstance(messages, tuple):
messages = list(messages) messages = list(messages)
@@ -119,6 +118,24 @@ class Logger(LoggerABC):
return message return message
def _console_format_message(self, level: str, timestamp, *messages: Messages) -> str:
if isinstance(messages, tuple):
messages = list(messages)
if not isinstance(messages, list):
messages = [messages]
messages = [str(message) for message in messages if message is not None]
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): def header(self, string: str):
self._log(LogLevel.info, string) self._log(LogLevel.info, string)

View File

@@ -11,7 +11,10 @@ class LoggerABC(ABC):
def set_level(self, level: LogLevel): ... def set_level(self, level: LogLevel): ...
@abstractmethod @abstractmethod
def _format_message(self, level: str, timestamp, *messages: Messages) -> str: ... def _file_format_message(self, level: str, timestamp, *messages: Messages) -> str: ...
@abstractmethod
def _console_format_message(self, level: str, timestamp, *messages: Messages) -> str: ...
@abstractmethod @abstractmethod
def header(self, string: str): def header(self, string: str):

View File

@@ -1,15 +1,13 @@
import asyncio import asyncio
import importlib.util import importlib.util
import json import json
import traceback
from datetime import datetime from datetime import datetime
from starlette.requests import Request from starlette.requests import Request
from cpl.core.log.log_level import LogLevel
from cpl.core.log.logger import Logger from cpl.core.log.logger import Logger
from cpl.core.typing import Source, Messages from cpl.core.typing import Source, Messages
from cpl.dependency import get_provider from cpl.dependency.context import get_provider
class StructuredLogger(Logger): class StructuredLogger(Logger):
@@ -21,18 +19,7 @@ class StructuredLogger(Logger):
def log_file(self): def log_file(self):
return f"logs/{self._file_prefix}_{datetime.now().strftime('%Y-%m-%d')}.jsonl" return f"logs/{self._file_prefix}_{datetime.now().strftime('%Y-%m-%d')}.jsonl"
def _log(self, level: LogLevel, *messages: Messages): def _file_format_message(self, level: str, timestamp: str, *messages: Messages) -> str:
try:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
formatted_message = self._format_message(level.value, timestamp, *messages)
structured_message = self._get_structured_message(level.value, timestamp, formatted_message)
self._write_log_to_file(level, structured_message)
self._write_to_console(level, formatted_message)
except Exception as e:
print(f"Error while logging: {e} -> {traceback.format_exc()}")
def _get_structured_message(self, level: str, timestamp: str, messages: str) -> str:
structured_message = { structured_message = {
"timestamp": timestamp, "timestamp": timestamp,
"level": level.upper(), "level": level.upper(),

View File

@@ -1,7 +1,7 @@
import inspect import inspect
from typing import Type from typing import Type
from cpl.core.log import LoggerABC, LogLevel from cpl.core.log import LoggerABC, LogLevel, StructuredLogger
from cpl.core.typing import Messages from cpl.core.typing import Messages
from cpl.dependency.inject import inject from cpl.dependency.inject import inject
from cpl.dependency.service_provider import ServiceProvider from cpl.dependency.service_provider import ServiceProvider
@@ -31,8 +31,11 @@ class WrappedLogger(LoggerABC):
def set_level(self, level: LogLevel): def set_level(self, level: LogLevel):
self._logger.set_level(level) self._logger.set_level(level)
def _format_message(self, level: str, timestamp, *messages: Messages) -> str: def _file_format_message(self, level: str, timestamp, *messages: Messages) -> str:
return self._logger._format_message(level, timestamp, *messages) return self._logger._file_format_message(level, timestamp, *messages)
def _console_format_message(self, level: str, timestamp, *messages: Messages) -> str:
return self._logger._console_format_message(level, timestamp, *messages)
@staticmethod @staticmethod
def _get_source() -> str | None: def _get_source() -> str | None:
@@ -48,6 +51,7 @@ class WrappedLogger(LoggerABC):
ServiceCollection, ServiceCollection,
WrappedLogger, WrappedLogger,
WrappedLogger.__subclasses__(), WrappedLogger.__subclasses__(),
StructuredLogger,
] ]
ignore_modules = [x.__module__ for x in ignore_classes if isinstance(x, type)] ignore_modules = [x.__module__ for x in ignore_classes if isinstance(x, type)]

View File

@@ -21,4 +21,4 @@ class DatabaseSettings(ConfigurationModelABC):
self.option("use_unicode", bool, False) self.option("use_unicode", bool, False)
self.option("buffered", bool, False) self.option("buffered", bool, False)
self.option("auth_plugin", str, "caching_sha2_password") self.option("auth_plugin", str, "caching_sha2_password")
self.option("ssl_disabled", bool, False) self.option("ssl_disabled", bool, True)

View File

@@ -22,27 +22,27 @@ class MySQLPool:
"use_unicode": database_settings.use_unicode, "use_unicode": database_settings.use_unicode,
"buffered": database_settings.buffered, "buffered": database_settings.buffered,
"auth_plugin": database_settings.auth_plugin, "auth_plugin": database_settings.auth_plugin,
"ssl_disabled": False, "ssl_disabled": database_settings.ssl_disabled,
} }
self._pool: Optional[MySQLConnectionPool] = None self._pool: Optional[MySQLConnectionPool] = None
async def _get_pool(self): async def _get_pool(self):
if self._pool is None: if self._pool is None:
self._pool = MySQLConnectionPool(
pool_name="mypool", pool_size=Environment.get("DB_POOL_SIZE", int, 1), **self._dbconfig
)
await self._pool.initialize_pool()
con = await self._pool.get_connection()
try: try:
self._pool = MySQLConnectionPool(
pool_name="mypool", pool_size=Environment.get("DB_POOL_SIZE", int, 1), **self._dbconfig
)
await self._pool.initialize_pool()
con = await self._pool.get_connection()
async with await con.cursor() as cursor: async with await con.cursor() as cursor:
await cursor.execute("SELECT 1") await cursor.execute("SELECT 1")
await cursor.fetchall() await cursor.fetchall()
await con.close()
except Exception as e: except Exception as e:
logger = get_provider().get_service(DBLogger) logger = get_provider().get_service(DBLogger)
logger.fatal(f"Error connecting to the database: {e}") logger.fatal(f"Error connecting to the database", e)
finally:
await con.close()
return self._pool return self._pool

View File

@@ -7,7 +7,7 @@ from psycopg_pool import AsyncConnectionPool, PoolTimeout
from cpl.core.environment import Environment from cpl.core.environment import Environment
from cpl.database.logger import DBLogger from cpl.database.logger import DBLogger
from cpl.database.model import DatabaseSettings from cpl.database.model import DatabaseSettings
from cpl.dependency import ServiceProvider from cpl.dependency.context import get_provider
class PostgresPool: class PostgresPool:
@@ -31,15 +31,16 @@ class PostgresPool:
pool = AsyncConnectionPool( pool = AsyncConnectionPool(
conninfo=self._conninfo, open=False, min_size=1, max_size=Environment.get("DB_POOL_SIZE", int, 1) conninfo=self._conninfo, open=False, min_size=1, max_size=Environment.get("DB_POOL_SIZE", int, 1)
) )
await pool.open()
try: try:
await pool.open()
async with pool.connection() as con: async with pool.connection() as con:
await pool.check_connection(con) await pool.check_connection(con)
self._pool = pool
except PoolTimeout as e: except PoolTimeout as e:
await pool.close() await pool.close()
logger = get_provider().get_service(DBLogger) logger = get_provider().get_service(DBLogger)
logger.fatal(f"Failed to connect to the database", e) logger.fatal(f"Failed to connect to the database", e)
self._pool = pool
return self._pool return self._pool

View File

@@ -1,4 +1,5 @@
import copy import copy
import inspect
import typing import typing
from contextlib import contextmanager from contextlib import contextmanager
from inspect import signature, Parameter, Signature from inspect import signature, Parameter, Signature
@@ -77,6 +78,35 @@ class ServiceProvider:
return implementations return implementations
def _get_source(self):
stack = inspect.stack()
if len(stack) <= 1:
return None
from cpl.dependency.service_collection import ServiceCollection
ignore_classes = [
ServiceProvider,
ServiceProvider.__subclasses__(),
ServiceCollection,
]
ignore_modules = [x.__module__ for x in ignore_classes if isinstance(x, type)]
for i, frame_info in enumerate(stack[1:]):
module = inspect.getmodule(frame_info.frame)
if module is None:
continue
if module.__name__ in ignore_classes or module in ignore_classes:
continue
if module in ignore_modules or module.__name__ in ignore_modules:
continue
if module.__name__ != __name__:
return module.__name__
def _build_by_signature(self, sig: Signature, origin_service_type: type = None) -> list[T]: def _build_by_signature(self, sig: Signature, origin_service_type: type = None) -> list[T]:
params = [] params = []
for param in sig.parameters.items(): for param in sig.parameters.items():
@@ -88,7 +118,11 @@ class ServiceProvider:
) )
elif parameter.annotation == Source: elif parameter.annotation == Source:
params.append(origin_service_type.__name__) params.append(
origin_service_type.__name__
if inspect.isclass(origin_service_type)
else str(origin_service_type)
)
elif issubclass(parameter.annotation, ServiceProvider): elif issubclass(parameter.annotation, ServiceProvider):
params.append(self) params.append(self)
@@ -116,6 +150,9 @@ class ServiceProvider:
else: else:
service_type = descriptor.service_type service_type = descriptor.service_type
if origin_service_type is None:
origin_service_type = self._get_source()
if origin_service_type is None: if origin_service_type is None:
origin_service_type = service_type origin_service_type = service_type