diff --git a/apps/authentication/common/log/base_logger.py b/apps/authentication/common/log/base_logger.py index 122dfdb..49e773d 100644 --- a/apps/authentication/common/log/base_logger.py +++ b/apps/authentication/common/log/base_logger.py @@ -1,53 +1,54 @@ from loguru import logger as guru_logger from common.config.log_settings import log_settings -from typing import List +from typing import Dict, Any import socket -import json -import logging import threading class LoggerBase: - binded_loggers = {} + binded_loggers = {} # Stores logger instances + loguru_sinks_added = set() # Tracks added log sinks logger_lock = threading.Lock() - def __init__( - self, logger_name: str, extra_fileds: dict[str, any] - ) -> None: + def __init__(self, logger_name: str, extra_fields: Dict[str, Any] = None) -> None: self.__logger_name = logger_name - self.extra_fileds = extra_fileds + self.extra_fields = extra_fields or {} + with LoggerBase.logger_lock: + # ✅ **If already created, reuse it** to prevent duplicates if self.__logger_name in LoggerBase.binded_loggers: self.logger = LoggerBase.binded_loggers[self.__logger_name] return - log_filename = ( - log_settings.LOG_PATH_BASE + "/" + self.__logger_name + ".log" - ) - log_retention = log_settings.LOG_RETENTION - log_rotation = log_settings.LOG_ROTATION - log_level = "INFO" - log_message_format = "{message}" + log_filename = f"{log_settings.LOG_BASE_PATH}/{self.__logger_name}.log" - guru_logger.add( - sink=log_filename, - level=log_level, - retention=log_retention, - rotation=log_rotation, - format=log_message_format, - serialize=True, - filter=lambda record: "extra" in record - and "topic" in record["extra"] - and record["extra"]["topic"] == self.__logger_name, - ) - host_name = socket.gethostname() - host_ip = socket.gethostbyname(host_name) - self.logger = guru_logger.bind( - topic=self.__logger_name, - host_ip=host_ip, - host_name=host_name, - ) - with LoggerBase.logger_lock: + # ✅ **Ensure Loguru sink is added only once** + if log_filename not in LoggerBase.loguru_sinks_added: + guru_logger.add( + sink=log_filename, + level="INFO", + retention=log_settings.LOG_RETENTION, + rotation=log_settings.LOG_ROTATION, + format="{message}", + serialize=True, + filter=lambda record: "extra" in record + and "topic" in record["extra"] + and record["extra"]["topic"] == self.__logger_name, + ) + LoggerBase.loguru_sinks_added.add(log_filename) # ✅ Mark as added + + host_name = socket.gethostname() + host_ip = socket.gethostbyname(host_name) + + # ✅ Bind the logger with topic and extra fields + self.logger = guru_logger.bind( + topic=self.__logger_name, + host_ip=host_ip, + host_name=host_name, + **self.extra_fields, # Include additional metadata + ) + + # ✅ Store reference to prevent duplicate instances LoggerBase.binded_loggers[self.__logger_name] = self.logger async def log_event( @@ -56,15 +57,15 @@ class LoggerBase: receiver_id: str, subject: str, event: str, - properties: dict[str, any], - text: str = "" + properties: Dict[str, Any], + text: str = "", ) -> None: local_logger = self.logger.bind( sender_id=sender_id, receiver_id=receiver_id, subject=subject, event=event, - properties=properties + properties=properties, ) local_logger.info(text) @@ -75,7 +76,7 @@ class LoggerBase: subject: str, exception: Exception, text: str = "", - properties: dict[str, any] = None, + properties: Dict[str, Any] = None, ) -> None: local_logger = self.logger.bind( sender_id=sender_id, @@ -83,7 +84,7 @@ class LoggerBase: subject=subject, event="exception", properties=properties, - exception=exception + exception=exception, ) local_logger.exception(text) @@ -93,7 +94,7 @@ class LoggerBase: receiver_id: str, subject: str, text: str = "", - properties: dict[str, any] = None, + properties: Dict[str, Any] = None, ) -> None: local_logger = self.logger.bind( sender_id=sender_id, @@ -103,14 +104,14 @@ class LoggerBase: properties=properties, ) local_logger.info(text) - + async def log_warning( self, sender_id: str, receiver_id: str, subject: str, text: str = "", - properties: dict[str, any] = None, + properties: Dict[str, Any] = None, ) -> None: local_logger = self.logger.bind( sender_id=sender_id, @@ -120,14 +121,14 @@ class LoggerBase: properties=properties, ) local_logger.warning(text) - + async def log_error( self, sender_id: str, receiver_id: str, subject: str, text: str = "", - properties: dict[str, any] = None, + properties: Dict[str, Any] = None, ) -> None: local_logger = self.logger.bind( sender_id=sender_id, diff --git a/apps/authentication/common/log/business_metric_logger.py b/apps/authentication/common/log/business_metric_logger.py deleted file mode 100644 index 95383ab..0000000 --- a/apps/authentication/common/log/business_metric_logger.py +++ /dev/null @@ -1,25 +0,0 @@ -from .base_logger import LoggerBase -from common.config.app_settings import app_settings -import json - - -class BusinessMetricLogger(LoggerBase): - def __init__(self, business_metrics: dict[str, any] = {}) -> None: - extra_fileds = {} - if business_metrics: - extra_fileds.update(business_metrics) - super().__init__( - logger_name=app_settings.BUSINESS_METRIC_LOG, - extra_fileds=extra_fileds, - ) - - - async def log_metrics(self, business_metrics: dict[str, any] = {}) -> None: - return await super().log_event( - sender_id="business_metric_manager", - receiver_id="business_metric_logger", - subject="metrics", - event="logging", - properties=business_metrics, - text="business metric logged" - ) diff --git a/apps/authentication/common/log/user_logger.py b/apps/authentication/common/log/user_logger.py deleted file mode 100644 index d931975..0000000 --- a/apps/authentication/common/log/user_logger.py +++ /dev/null @@ -1,14 +0,0 @@ -from .base_logger import LoggerBase -from common.config.app_settings import app_settings - -import json - - -class UserLogger(LoggerBase): - def __init__(self, user_activities: dict[str, any] = {}) -> None: - extra_fileds = {} - if user_activities: - extra_fileds.update(user_activities) - super().__init__( - logger_name=app_settings.USER_ACTIVITY_LOG, extra_fileds=extra_fileds - ) diff --git a/apps/authentication/webapi/bootstrap/application.py b/apps/authentication/webapi/bootstrap/application.py index fd9f1b1..e060fbf 100644 --- a/apps/authentication/webapi/bootstrap/application.py +++ b/apps/authentication/webapi/bootstrap/application.py @@ -3,7 +3,7 @@ from fastapi import FastAPI from fastapi.openapi.utils import get_openapi from webapi.providers import common -from webapi.providers import logger +from webapi.providers.logger import register_logger from webapi.providers import router from webapi.providers import database @@ -17,9 +17,9 @@ def create_app() -> FastAPI: app = FreeleapsApp() + register_logger() register(app, exception_handler) register(app, database) - register(app, logger) register(app, router) # register(app, scheduler) register(app, common) diff --git a/apps/authentication/webapi/providers/logger.py b/apps/authentication/webapi/providers/logger.py index b9fd1b4..afcab10 100644 --- a/apps/authentication/webapi/providers/logger.py +++ b/apps/authentication/webapi/providers/logger.py @@ -1,56 +1,47 @@ import logging import sys -from loguru import logger -from common.config.log_settings import log_settings +from loguru import logger as guru_logger -def register(app=None): - level = log_settings.LOG_LEVEL - file_path = log_settings.LOG_PATH - retention = log_settings.LOG_RETENTION - rotation = log_settings.LOG_ROTATION +def register_logger(): + print("📢 Setting up logging interception...") - # intercept everything at the root logger + # 🔴 **Ensure Uvicorn Logs Are Captured** + intercept_loggers = ["uvicorn", "uvicorn.access", "uvicorn.error", "fastapi"] + + class InterceptHandler(logging.Handler): + def emit(self, record): + level = ( + guru_logger.level(record.levelname).name + if guru_logger.level(record.levelname, None) + else record.levelno + ) + frame, depth = logging.currentframe(), 2 + while frame.f_code.co_filename == logging.__file__: + frame = frame.f_back + depth += 1 + + guru_logger.opt(depth=depth, exception=record.exc_info).log( + level, + f"[{record.name}] {record.getMessage()}", + ) + + # 🔴 **Replace Existing Loggers with Interception** + logging.root.handlers.clear() + logging.root.setLevel(logging.INFO) logging.root.handlers = [InterceptHandler()] - logging.root.setLevel(level) - # remove every other logger's handlers - # and propagate to root logger - for name in logging.root.manager.loggerDict.keys(): - logging.getLogger(name).handlers = [] - logging.getLogger(name).propagate = True + for logger_name in intercept_loggers: + logging_logger = logging.getLogger(logger_name) + logging_logger.handlers.clear() # Remove Uvicorn default handlers + logging_logger.propagate = True # ✅ Ensure they propagate through Loguru - # configure loguru - logger.add(sink=sys.stdout) - logger.add(sink=file_path, level=level, retention=retention, rotation=rotation) + # 🔴 **Redirect stdout/stderr to Loguru (Keep Green Timestamps)** + guru_logger.remove() + guru_logger.add( + sys.stdout, + level="INFO", + format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level} | {message}", + ) - logger.disable("pika.adapters") - logger.disable("pika.connection") - logger.disable("pika.channel") - logger.disable("pika.callback") - logger.disable("pika.frame") - logger.disable("pika.spec") - logger.disable("aiormq.connection") - logger.disable("urllib3.connectionpool") - logging.getLogger("pymongo").setLevel( - logging.WARNING - ) # Suppress pymongo DEBUG logs - - -class InterceptHandler(logging.Handler): - def emit(self, record): - # Get corresponding Loguru level if it exists - try: - level = logger.level(record.levelname).name - except ValueError: - level = record.levelno - - # Find caller from where originated the logged message - frame, depth = logging.currentframe(), 2 - while frame.f_code.co_filename == logging.__file__: - frame = frame.f_back - depth += 1 - - logger.opt(depth=depth, exception=record.exc_info).log( - level, record.getMessage() - ) + print("✅ Logging interception complete. Logs are formatted and deduplicated!") diff --git a/apps/content/.env b/apps/content/.env new file mode 100644 index 0000000..aaad366 --- /dev/null +++ b/apps/content/.env @@ -0,0 +1,25 @@ +APP_NAME=content +export SERVICE_API_ACCESS_HOST=0.0.0.0 +export SERVICE_API_ACCESS_PORT=8013 +export MONGODB_NAME=freeleaps2 +export MONGODB_PORT=27017 +export CONTAINER_APP_ROOT=/app +export FREELEAPS_WWW_AS_AZURE_CLIENT_SECRET=3gK8Q~PJbyWmiNqaGgho2ZqCY~OXzABSyN8wWasK +export LOG_BASE_PATH=$CONTAINER_APP_ROOT/log/$APP_NAME +export BACKEND_LOG_FILE_NAME=$APP_NAME +export APPLICATION_ACTIVITY_LOG=$APP_NAME-activity +GIT_REPO_ROOT=/mnt/freeleaps/freeleaps-service-hub +CODEBASE_ROOT=/mnt/freeleaps/freeleaps-service-hub/apps/content +SITE_DEPLOY_FOLDER=/mnt/freeleaps/freeleaps-service-hub/sites/content/deploy +#!/bin/bash +export VENV_DIR=venv_t +export VENV_ACTIVATE=venv_t/bin/activate +export DOCKER_HOME=/var/lib/docker +export DOCKER_APP_HOME=$DOCKER_HOME/app +export DOCKER_BACKEND_HOME=$DOCKER_APP_HOME/$APP_NAME +export DOCKER_BACKEND_LOG_HOME=$DOCKER_BACKEND_HOME/log +export MONGODB_URI=mongodb://localhost:27017/ +export CENTRAL_STORAGE_WEBAPI_URL_BASE="http://localhost:8005/api/central_storage" +export FREELEAPS_ENV=local +export LOG_BASE_PATH=${CODEBASE_ROOT}/log + diff --git a/apps/notification/common/log/base_logger.py b/apps/notification/common/log/base_logger.py index 972e20f..49e773d 100644 --- a/apps/notification/common/log/base_logger.py +++ b/apps/notification/common/log/base_logger.py @@ -1,49 +1,54 @@ from loguru import logger as guru_logger from common.config.log_settings import log_settings -from typing import List +from typing import Dict, Any import socket -import json -import logging import threading class LoggerBase: - binded_loggers = {} + binded_loggers = {} # Stores logger instances + loguru_sinks_added = set() # Tracks added log sinks logger_lock = threading.Lock() - def __init__(self, logger_name: str, extra_fields: dict[str, any]) -> None: + def __init__(self, logger_name: str, extra_fields: Dict[str, Any] = None) -> None: self.__logger_name = logger_name - self.extra_fields = extra_fields + self.extra_fields = extra_fields or {} + with LoggerBase.logger_lock: + # ✅ **If already created, reuse it** to prevent duplicates if self.__logger_name in LoggerBase.binded_loggers: self.logger = LoggerBase.binded_loggers[self.__logger_name] return - log_filename = log_settings.LOG_BASE_PATH + "/" + self.__logger_name + ".log" - log_retention = log_settings.LOG_RETENTION - log_rotation = log_settings.LOG_ROTATION - log_level = "INFO" - log_message_format = "{message}" + log_filename = f"{log_settings.LOG_BASE_PATH}/{self.__logger_name}.log" - guru_logger.add( - sink=log_filename, - level=log_level, - retention=log_retention, - rotation=log_rotation, - format=log_message_format, - serialize=True, - filter=lambda record: "extra" in record - and "topic" in record["extra"] - and record["extra"]["topic"] == self.__logger_name, - ) - host_name = socket.gethostname() - host_ip = socket.gethostbyname(host_name) - self.logger = guru_logger.bind( - topic=self.__logger_name, - host_ip=host_ip, - host_name=host_name, - ) - with LoggerBase.logger_lock: + # ✅ **Ensure Loguru sink is added only once** + if log_filename not in LoggerBase.loguru_sinks_added: + guru_logger.add( + sink=log_filename, + level="INFO", + retention=log_settings.LOG_RETENTION, + rotation=log_settings.LOG_ROTATION, + format="{message}", + serialize=True, + filter=lambda record: "extra" in record + and "topic" in record["extra"] + and record["extra"]["topic"] == self.__logger_name, + ) + LoggerBase.loguru_sinks_added.add(log_filename) # ✅ Mark as added + + host_name = socket.gethostname() + host_ip = socket.gethostbyname(host_name) + + # ✅ Bind the logger with topic and extra fields + self.logger = guru_logger.bind( + topic=self.__logger_name, + host_ip=host_ip, + host_name=host_name, + **self.extra_fields, # Include additional metadata + ) + + # ✅ Store reference to prevent duplicate instances LoggerBase.binded_loggers[self.__logger_name] = self.logger async def log_event( @@ -52,7 +57,7 @@ class LoggerBase: receiver_id: str, subject: str, event: str, - properties: dict[str, any], + properties: Dict[str, Any], text: str = "", ) -> None: local_logger = self.logger.bind( @@ -71,7 +76,7 @@ class LoggerBase: subject: str, exception: Exception, text: str = "", - properties: dict[str, any] = None, + properties: Dict[str, Any] = None, ) -> None: local_logger = self.logger.bind( sender_id=sender_id, @@ -89,7 +94,7 @@ class LoggerBase: receiver_id: str, subject: str, text: str = "", - properties: dict[str, any] = None, + properties: Dict[str, Any] = None, ) -> None: local_logger = self.logger.bind( sender_id=sender_id, @@ -106,7 +111,7 @@ class LoggerBase: receiver_id: str, subject: str, text: str = "", - properties: dict[str, any] = None, + properties: Dict[str, Any] = None, ) -> None: local_logger = self.logger.bind( sender_id=sender_id, @@ -123,7 +128,7 @@ class LoggerBase: receiver_id: str, subject: str, text: str = "", - properties: dict[str, any] = None, + properties: Dict[str, Any] = None, ) -> None: local_logger = self.logger.bind( sender_id=sender_id, diff --git a/apps/payment/common/log/base_logger.py b/apps/payment/common/log/base_logger.py index 972e20f..49e773d 100644 --- a/apps/payment/common/log/base_logger.py +++ b/apps/payment/common/log/base_logger.py @@ -1,49 +1,54 @@ from loguru import logger as guru_logger from common.config.log_settings import log_settings -from typing import List +from typing import Dict, Any import socket -import json -import logging import threading class LoggerBase: - binded_loggers = {} + binded_loggers = {} # Stores logger instances + loguru_sinks_added = set() # Tracks added log sinks logger_lock = threading.Lock() - def __init__(self, logger_name: str, extra_fields: dict[str, any]) -> None: + def __init__(self, logger_name: str, extra_fields: Dict[str, Any] = None) -> None: self.__logger_name = logger_name - self.extra_fields = extra_fields + self.extra_fields = extra_fields or {} + with LoggerBase.logger_lock: + # ✅ **If already created, reuse it** to prevent duplicates if self.__logger_name in LoggerBase.binded_loggers: self.logger = LoggerBase.binded_loggers[self.__logger_name] return - log_filename = log_settings.LOG_BASE_PATH + "/" + self.__logger_name + ".log" - log_retention = log_settings.LOG_RETENTION - log_rotation = log_settings.LOG_ROTATION - log_level = "INFO" - log_message_format = "{message}" + log_filename = f"{log_settings.LOG_BASE_PATH}/{self.__logger_name}.log" - guru_logger.add( - sink=log_filename, - level=log_level, - retention=log_retention, - rotation=log_rotation, - format=log_message_format, - serialize=True, - filter=lambda record: "extra" in record - and "topic" in record["extra"] - and record["extra"]["topic"] == self.__logger_name, - ) - host_name = socket.gethostname() - host_ip = socket.gethostbyname(host_name) - self.logger = guru_logger.bind( - topic=self.__logger_name, - host_ip=host_ip, - host_name=host_name, - ) - with LoggerBase.logger_lock: + # ✅ **Ensure Loguru sink is added only once** + if log_filename not in LoggerBase.loguru_sinks_added: + guru_logger.add( + sink=log_filename, + level="INFO", + retention=log_settings.LOG_RETENTION, + rotation=log_settings.LOG_ROTATION, + format="{message}", + serialize=True, + filter=lambda record: "extra" in record + and "topic" in record["extra"] + and record["extra"]["topic"] == self.__logger_name, + ) + LoggerBase.loguru_sinks_added.add(log_filename) # ✅ Mark as added + + host_name = socket.gethostname() + host_ip = socket.gethostbyname(host_name) + + # ✅ Bind the logger with topic and extra fields + self.logger = guru_logger.bind( + topic=self.__logger_name, + host_ip=host_ip, + host_name=host_name, + **self.extra_fields, # Include additional metadata + ) + + # ✅ Store reference to prevent duplicate instances LoggerBase.binded_loggers[self.__logger_name] = self.logger async def log_event( @@ -52,7 +57,7 @@ class LoggerBase: receiver_id: str, subject: str, event: str, - properties: dict[str, any], + properties: Dict[str, Any], text: str = "", ) -> None: local_logger = self.logger.bind( @@ -71,7 +76,7 @@ class LoggerBase: subject: str, exception: Exception, text: str = "", - properties: dict[str, any] = None, + properties: Dict[str, Any] = None, ) -> None: local_logger = self.logger.bind( sender_id=sender_id, @@ -89,7 +94,7 @@ class LoggerBase: receiver_id: str, subject: str, text: str = "", - properties: dict[str, any] = None, + properties: Dict[str, Any] = None, ) -> None: local_logger = self.logger.bind( sender_id=sender_id, @@ -106,7 +111,7 @@ class LoggerBase: receiver_id: str, subject: str, text: str = "", - properties: dict[str, any] = None, + properties: Dict[str, Any] = None, ) -> None: local_logger = self.logger.bind( sender_id=sender_id, @@ -123,7 +128,7 @@ class LoggerBase: receiver_id: str, subject: str, text: str = "", - properties: dict[str, any] = None, + properties: Dict[str, Any] = None, ) -> None: local_logger = self.logger.bind( sender_id=sender_id,