""" main.py 报告模板管理模块 FastAPI 应用入口。 启动: uvicorn main:app --host 0.0.0.0 --port 8100 或: python main.py """ from __future__ import annotations import time import uuid from contextlib import asynccontextmanager from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from config import settings from database import init_database from log import configure_logging, get_logger from routers import template # 在创建应用前完成日志配置 configure_logging() logger = get_logger("app") access_logger = get_logger("app.access") @asynccontextmanager async def lifespan(_app: FastAPI): logger.info("应用启动 | %s v%s", settings.APP_TITLE, settings.APP_VERSION) if settings.DB_AUTO_CREATE_TABLES: try: init_database() except Exception as e: # noqa: BLE001 logger.warning("启动建表失败(不影响已存在表的使用): %s", e) yield logger.info("应用关闭") app = FastAPI( title=settings.APP_TITLE, version=settings.APP_VERSION, description=settings.APP_DESCRIPTION, lifespan=lifespan, ) app.add_middleware( CORSMiddleware, allow_origins=settings.CORS_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.middleware("http") async def log_requests(request: Request, call_next): if not settings.LOG_HTTP_ACCESS: return await call_next(request) req_id = uuid.uuid4().hex[:8] start = time.perf_counter() client = request.client.host if request.client else "-" access_logger.info("→ [%s] %s %s | client=%s", req_id, request.method, request.url.path, client) try: response = await call_next(request) except Exception: cost = (time.perf_counter() - start) * 1000 access_logger.exception("✗ [%s] %s %s | %.1fms | 未处理异常", req_id, request.method, request.url.path, cost) raise cost = (time.perf_counter() - start) * 1000 access_logger.info( "← [%s] %s %s | %s | %.1fms", req_id, request.method, request.url.path, response.status_code, cost, ) response.headers["X-Request-ID"] = req_id return response app.include_router(template.router) @app.get("/health", tags=["健康检查"]) def health() -> dict: return {"status": "ok", "version": settings.APP_VERSION} if __name__ == "__main__": import uvicorn # log_config=None:沿用本模块 configure_logging() 的配置,避免被 uvicorn 覆盖 uvicorn.run( "main:app", host=settings.HOST, port=settings.PORT, reload=settings.RELOAD, log_config=None, )