mirror of
https://github.com/grillazz/fastapi-sqlalchemy-asyncpg.git
synced 2026-03-06 10:00:39 +03:00
refactor: update logger import paths to use app.services.logging
This commit is contained in:
@@ -2,7 +2,7 @@ from typing import Annotated
|
|||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query, Request, status
|
from fastapi import APIRouter, Depends, Query, Request, status
|
||||||
from pydantic import EmailStr
|
from pydantic import EmailStr
|
||||||
from rotoger import get_logger
|
from app.services.logging import get_logger
|
||||||
from starlette.concurrency import run_in_threadpool
|
from starlette.concurrency import run_in_threadpool
|
||||||
|
|
||||||
from app.services.smtp import SMTPEmailService
|
from app.services.smtp import SMTPEmailService
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from typing import Annotated
|
|||||||
|
|
||||||
from fastapi import APIRouter, Depends, Form
|
from fastapi import APIRouter, Depends, Form
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from rotoger import get_logger
|
from app.services.logging import get_logger
|
||||||
|
|
||||||
from app.services.llm import get_llm_service
|
from app.services.llm import get_llm_service
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||||
from rotoger import get_logger
|
from app.services.logging import get_logger
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Form, HTTPException, Request, status
|
from fastapi import APIRouter, Depends, Form, HTTPException, Request, status
|
||||||
from rotoger import get_logger
|
from app.services.logging import get_logger
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.database import get_db
|
from app.database import get_db
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
|
|
||||||
from fastapi.exceptions import ResponseValidationError
|
from fastapi.exceptions import ResponseValidationError
|
||||||
from rotoger import get_logger
|
from app.services.logging import get_logger
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import orjson
|
import orjson
|
||||||
from attrs import define, field
|
from attrs import define, field
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from rotoger import get_logger
|
from app.services.logging import get_logger
|
||||||
|
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import asyncpg
|
|||||||
from fastapi import Depends, FastAPI, Request
|
from fastapi import Depends, FastAPI, Request
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from rotoger import get_logger
|
from app.services.logging import get_logger
|
||||||
from starlette.middleware import Middleware
|
from starlette.middleware import Middleware
|
||||||
from starlette.middleware.gzip import GZipMiddleware
|
from starlette.middleware.gzip import GZipMiddleware
|
||||||
|
|
||||||
@@ -21,12 +21,13 @@ from app.middleware.profiler import ProfilingMiddleware
|
|||||||
from app.redis import get_redis
|
from app.redis import get_redis
|
||||||
from app.services.auth import AuthBearer
|
from app.services.auth import AuthBearer
|
||||||
|
|
||||||
logger = get_logger()
|
# logger = get_logger()
|
||||||
templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates")
|
templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates")
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
|
app.logger = get_logger()
|
||||||
app.redis = await get_redis()
|
app.redis = await get_redis()
|
||||||
postgres_dsn = global_settings.postgres_url.unicode_string()
|
postgres_dsn = global_settings.postgres_url.unicode_string()
|
||||||
try:
|
try:
|
||||||
@@ -35,12 +36,12 @@ async def lifespan(app: FastAPI):
|
|||||||
min_size=5,
|
min_size=5,
|
||||||
max_size=20,
|
max_size=20,
|
||||||
)
|
)
|
||||||
await logger.ainfo(
|
await app.logger.ainfo(
|
||||||
"Postgres pool created", idle_size=app.postgres_pool.get_idle_size()
|
"Postgres pool created", idle_size=app.postgres_pool.get_idle_size()
|
||||||
)
|
)
|
||||||
yield
|
yield
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await logger.aerror("Error during app startup", error=repr(e))
|
await app.logger.aerror("Error during app startup", error=repr(e))
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
await app.redis.close()
|
await app.redis.close()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from typing import Any
|
|||||||
|
|
||||||
from asyncpg import UniqueViolationError
|
from asyncpg import UniqueViolationError
|
||||||
from fastapi import HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
from rotoger import get_logger
|
from app.services.logging import get_logger
|
||||||
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
|
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import DeclarativeBase, declared_attr
|
from sqlalchemy.orm import DeclarativeBase, declared_attr
|
||||||
|
|||||||
19
app/server.py
Normal file
19
app/server.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from granian import Granian
|
||||||
|
|
||||||
|
def startup():
|
||||||
|
print("Server starting up...")
|
||||||
|
|
||||||
|
def shutdown():
|
||||||
|
print("Server shutting down...")
|
||||||
|
|
||||||
|
server = Granian(
|
||||||
|
"main:app",
|
||||||
|
host="0.0.0.0", # Bind to all interfaces
|
||||||
|
port=8000,
|
||||||
|
workers=4,
|
||||||
|
interface="asgi",
|
||||||
|
blocking_threads=8 # Optional: threads per worker for blocking ops
|
||||||
|
)
|
||||||
|
server.on_startup(startup)
|
||||||
|
server.on_shutdown(shutdown)
|
||||||
|
server.serve_forever()
|
||||||
@@ -3,7 +3,7 @@ import time
|
|||||||
import jwt
|
import jwt
|
||||||
from fastapi import HTTPException, Request
|
from fastapi import HTTPException, Request
|
||||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||||
from rotoger import get_logger
|
from app.services.logging import get_logger
|
||||||
|
|
||||||
from app.config import settings as global_settings
|
from app.config import settings as global_settings
|
||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
|
|||||||
96
app/services/logging.py
Normal file
96
app/services/logging.py
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import orjson
|
||||||
|
import structlog
|
||||||
|
from whenever._whenever import Instant
|
||||||
|
|
||||||
|
|
||||||
|
def _configure_logger() -> structlog.BoundLogger:
|
||||||
|
"""
|
||||||
|
Configures and returns a structlog logger with a rotating file handler.
|
||||||
|
|
||||||
|
The logger is configured using environment variables for path, file size,
|
||||||
|
and backup count. It formats logs as JSON.
|
||||||
|
"""
|
||||||
|
log_dir = Path(os.environ.get("ROTOGER_LOG_PATH", "."))
|
||||||
|
log_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
log_date = Instant.now().py_datetime().strftime("%Y%m%d")
|
||||||
|
log_path = log_dir / f"{log_date}_{os.getpid()}.log"
|
||||||
|
|
||||||
|
# Use int() to ensure env var values are correctly typed
|
||||||
|
max_bytes = int(os.environ.get("ROTOGER_LOG_MAX_BYTES", 10 * 1024 * 1024))
|
||||||
|
backup_count = int(os.environ.get("ROTOGER_LOG_BACKUP_COUNT", 5))
|
||||||
|
|
||||||
|
handler = RotatingFileHandler(
|
||||||
|
filename=log_path,
|
||||||
|
maxBytes=max_bytes,
|
||||||
|
backupCount=backup_count,
|
||||||
|
encoding="utf-8",
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
# Use structlog's standard library integration
|
||||||
|
structlog.configure(
|
||||||
|
processors=[
|
||||||
|
structlog.contextvars.merge_contextvars,
|
||||||
|
structlog.stdlib.add_log_level,
|
||||||
|
structlog.stdlib.PositionalArgumentsFormatter(),
|
||||||
|
structlog.processors.TimeStamper(fmt="iso", utc=True),
|
||||||
|
structlog.processors.format_exc_info,
|
||||||
|
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
|
||||||
|
# structlog.stdlib.add_logger_name,
|
||||||
|
],
|
||||||
|
logger_factory=structlog.stdlib.LoggerFactory(),
|
||||||
|
wrapper_class=structlog.stdlib.BoundLogger,
|
||||||
|
cache_logger_on_first_use=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configure the underlying standard logger
|
||||||
|
formatter = structlog.stdlib.ProcessorFormatter(
|
||||||
|
# These run after the processors defined in structlog.configure
|
||||||
|
foreign_pre_chain=[
|
||||||
|
structlog.contextvars.merge_contextvars,
|
||||||
|
structlog.stdlib.add_log_level,
|
||||||
|
structlog.stdlib.PositionalArgumentsFormatter(),
|
||||||
|
structlog.processors.TimeStamper(fmt="iso", utc=True),
|
||||||
|
structlog.processors.format_exc_info,
|
||||||
|
structlog.stdlib.add_logger_name,
|
||||||
|
],
|
||||||
|
processor=structlog.processors.JSONRenderer(
|
||||||
|
serializer=lambda *args, **kwargs: orjson.dumps(*args, **kwargs).decode()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
root_logger = logging.getLogger("root") # Get the root logger
|
||||||
|
root_logger.addHandler(handler)
|
||||||
|
root_logger.propagate = False # Prevent logs from being propagated to the root logger
|
||||||
|
root_logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
uvicorn_logger = logging.getLogger("uvicorn") # Get the root logger
|
||||||
|
uvicorn_logger.addHandler(handler)
|
||||||
|
uvicorn_logger.propagate = False # Prevent logs from being propagated to the root logger
|
||||||
|
uvicorn_logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
sa_logger = logging.getLogger("sqlalchemy") # Get the root logger
|
||||||
|
sa_logger.addHandler(handler)
|
||||||
|
sa_logger.propagate = False # Prevent logs from being propagated to the root logger
|
||||||
|
sa_logger.setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
# Set SQLAlchemy engine logger level specifically if needed
|
||||||
|
# logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
|
||||||
|
return structlog.get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Module-level singleton instance
|
||||||
|
_logger_instance = _configure_logger()
|
||||||
|
|
||||||
|
|
||||||
|
def get_logger() -> structlog.BoundLogger:
|
||||||
|
"""
|
||||||
|
Returns the configured singleton logger instance.
|
||||||
|
"""
|
||||||
|
return _logger_instance
|
||||||
@@ -3,7 +3,7 @@ from datetime import datetime
|
|||||||
from apscheduler import AsyncScheduler
|
from apscheduler import AsyncScheduler
|
||||||
from apscheduler.triggers.interval import IntervalTrigger
|
from apscheduler.triggers.interval import IntervalTrigger
|
||||||
from attrs import define
|
from attrs import define
|
||||||
from rotoger import get_logger
|
from app.services.logging import get_logger
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from starlette.types import ASGIApp, Receive, Scope, Send
|
from starlette.types import ASGIApp, Receive, Scope, Send
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from email.mime.text import MIMEText
|
|||||||
from attrs import define, field
|
from attrs import define, field
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from pydantic import EmailStr
|
from pydantic import EmailStr
|
||||||
from rotoger import get_logger
|
from app.services.logging import get_logger
|
||||||
|
|
||||||
from app.config import settings as global_settings
|
from app.config import settings as global_settings
|
||||||
from app.utils.singleton import SingletonMetaNoArgs
|
from app.utils.singleton import SingletonMetaNoArgs
|
||||||
|
|||||||
Reference in New Issue
Block a user