diff --git a/app/api/health.py b/app/api/health.py index 761c143..3a59010 100644 --- a/app/api/health.py +++ b/app/api/health.py @@ -6,9 +6,9 @@ from pydantic import EmailStr from starlette.concurrency import run_in_threadpool from app.services.smtp import SMTPEmailService -from app.utils.logging import AppLogger +from app.utils.logging import AppStructLogger -logger = AppLogger().get_logger() +logger = AppStructLogger().get_logger() router = APIRouter() diff --git a/app/api/ml.py b/app/api/ml.py index ca600a9..98d94f4 100644 --- a/app/api/ml.py +++ b/app/api/ml.py @@ -4,9 +4,9 @@ from fastapi import APIRouter, Depends, Form from fastapi.responses import StreamingResponse from app.services.llm import get_llm_service -from app.utils.logging import AppLogger +from app.utils.logging import AppStructLogger -logger = AppLogger().get_logger() +logger = AppStructLogger().get_logger() router = APIRouter() diff --git a/app/api/stuff.py b/app/api/stuff.py index 552bd20..07b39d8 100644 --- a/app/api/stuff.py +++ b/app/api/stuff.py @@ -5,9 +5,9 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.models.stuff import Stuff from app.schemas.stuff import StuffResponse, StuffSchema -from app.utils.logging import AppLogger +from app.utils.logging import AppStructLogger -logger = AppLogger().get_logger() +logger = AppStructLogger().get_logger() router = APIRouter(prefix="/v1/stuff") diff --git a/app/api/user.py b/app/api/user.py index 4b2d468..0d5b3bb 100644 --- a/app/api/user.py +++ b/app/api/user.py @@ -7,9 +7,9 @@ from app.database import get_db from app.models.user import User from app.schemas.user import TokenResponse, UserLogin, UserResponse, UserSchema from app.services.auth import create_access_token -from app.utils.logging import AppLogger +from app.utils.logging import AppStructLogger -logger = AppLogger().get_logger() +logger = AppStructLogger().get_logger() router = APIRouter(prefix="/v1/user") diff --git a/app/database.py b/app/database.py index d830b89..c900087 100644 --- a/app/database.py +++ b/app/database.py @@ -3,9 +3,9 @@ from collections.abc import AsyncGenerator from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine from app.config import settings as global_settings -from app.utils.logging import setup_structlog +from app.utils.logging import AppStructLogger -logger = setup_structlog() +logger = AppStructLogger().get_logger() engine = create_async_engine( global_settings.asyncpg_url.unicode_string(), diff --git a/app/main.py b/app/main.py index 7dd3403..70ca866 100644 --- a/app/main.py +++ b/app/main.py @@ -15,9 +15,9 @@ from app.api.user import router as user_router from app.config import settings as global_settings from app.redis import get_redis from app.services.auth import AuthBearer -from app.utils.logging import setup_structlog +from app.utils.logging import AppStructLogger -logger = setup_structlog() +logger = AppStructLogger().get_logger() templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates") @asynccontextmanager diff --git a/app/models/base.py b/app/models/base.py index bb03011..6f114b4 100644 --- a/app/models/base.py +++ b/app/models/base.py @@ -6,9 +6,9 @@ from sqlalchemy.exc import IntegrityError, SQLAlchemyError from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import DeclarativeBase, declared_attr -from app.utils.logging import AppLogger +from app.utils.logging import AppStructLogger -logger = AppLogger().get_logger() +logger = AppStructLogger().get_logger() class Base(DeclarativeBase): diff --git a/app/services/auth.py b/app/services/auth.py index 51fff89..3509f8a 100644 --- a/app/services/auth.py +++ b/app/services/auth.py @@ -6,9 +6,9 @@ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from app.config import settings as global_settings from app.models.user import User -from app.utils.logging import AppLogger +from app.utils.logging import AppStructLogger -logger = AppLogger().get_logger() +logger = AppStructLogger().get_logger() async def get_from_redis(request: Request, key: str): diff --git a/app/services/smtp.py b/app/services/smtp.py index 098a261..4f1df89 100644 --- a/app/services/smtp.py +++ b/app/services/smtp.py @@ -7,10 +7,10 @@ from fastapi.templating import Jinja2Templates from pydantic import EmailStr from app.config import settings as global_settings -from app.utils.logging import AppLogger +from app.utils.logging import AppStructLogger from app.utils.singleton import SingletonMetaNoArgs -logger = AppLogger().get_logger() +logger = AppStructLogger().get_logger() @define diff --git a/app/utils/logging.py b/app/utils/logging.py index e889639..49736f8 100644 --- a/app/utils/logging.py +++ b/app/utils/logging.py @@ -5,30 +5,9 @@ from pathlib import Path import orjson import structlog -from rich.console import Console -from rich.logging import RichHandler from whenever._whenever import Instant -from app.utils.singleton import SingletonMeta - - -class AppLogger(metaclass=SingletonMeta): - _logger = None - - def __init__(self): - self._logger = logging.getLogger(__name__) - - def get_logger(self): - return self._logger - - -class RichConsoleHandler(RichHandler): - def __init__(self, width=200, style=None, **kwargs): - super().__init__( - console=Console(color_system="256", width=width, style=style, stderr=True), - **kwargs, - ) - +from app.utils.singleton import SingletonMetaNoArgs # TODO: merge this wrapper with the one in structlog under one hood of AppLogger @@ -50,30 +29,39 @@ class BytesToTextIOWrapper: def close(self): self.handler.close() +# @define +class AppStructLogger(metaclass=SingletonMetaNoArgs): + _logger = None -def setup_structlog() -> structlog.BoundLogger: - log_date = Instant.now().py_datetime().strftime("%Y%m%d") - log_path = Path(f"{log_date}_{os.getpid()}.log") - handler = RotatingFileHandler( - filename=log_path, - mode="a", # text mode - maxBytes=10 * 1024 * 1024, - backupCount=5, - encoding="utf-8" - ) - file_like = BytesToTextIOWrapper(handler) - structlog.configure( - cache_logger_on_first_use=True, - wrapper_class=structlog.make_filtering_bound_logger(logging.INFO), - processors=[ - structlog.contextvars.merge_contextvars, - structlog.processors.add_log_level, - structlog.processors.format_exc_info, - structlog.processors.TimeStamper(fmt="iso", utc=True), - structlog.processors.JSONRenderer(serializer=orjson.dumps), - ], - logger_factory=structlog.BytesLoggerFactory( - file=file_like + def __init__(self): + _log_date = Instant.now().py_datetime().strftime("%Y%m%d") + _log_path = Path(f"{_log_date}_{os.getpid()}.log") + _handler = RotatingFileHandler( + filename=_log_path, + mode="a", # text mode + maxBytes=10 * 1024 * 1024, + backupCount=5, + encoding="utf-8" ) - ) - return structlog.get_logger() + structlog.configure( + cache_logger_on_first_use=True, + wrapper_class=structlog.make_filtering_bound_logger(logging.INFO), + processors=[ + structlog.contextvars.merge_contextvars, + structlog.processors.add_log_level, + structlog.processors.format_exc_info, + structlog.processors.TimeStamper(fmt="iso", utc=True), + structlog.processors.JSONRenderer(serializer=orjson.dumps), + ], + logger_factory=structlog.BytesLoggerFactory( + file=BytesToTextIOWrapper(_handler) + ) + ) + self._logger = structlog.get_logger() + + def get_logger(self) -> structlog.BoundLogger: + """ + Returns: + structlog.BoundLogger: The configured logger instance. + """ + return self._logger