Merge remote-tracking branch 'origin/main' into feat/export-service
# Conflicts: # .gitignore # backend/app/main.py
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
from contextlib import asynccontextmanager
|
||||
import asyncio
|
||||
from time import perf_counter
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
@@ -15,12 +18,16 @@ from app.local_model_routes import router as local_model_router
|
||||
from app.usage_routes import router as usage_router
|
||||
from app.provider_preview_routes import router as provider_preview_router
|
||||
from app.schemas import HealthResponse, ServiceStatusResponse
|
||||
from app.log_routes import router as log_router
|
||||
from app.operation_logs import install_logging, log_event, request_id, shutdown_logging
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(_: FastAPI):
|
||||
install_logging()
|
||||
log_event('system', 'service.started')
|
||||
# 重启后内存注册表为空,清理上一次运行遗留的导出产物,避免磁盘垃圾堆积。
|
||||
export_service.cleanup_orphan_files()
|
||||
from app.services import transcription_service
|
||||
@@ -28,6 +35,7 @@ async def lifespan(_: FastAPI):
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
await container.agent.shutdown()
|
||||
from app.services import index_service
|
||||
await index_service.shutdown()
|
||||
await transcription_service.shutdown()
|
||||
@@ -39,6 +47,8 @@ async def lifespan(_: FastAPI):
|
||||
# 第三方 MCP Server 必须跟随 AI Core 退出,不能遗留孤儿进程。
|
||||
container.plugins.shutdown()
|
||||
container.mcp_servers.shutdown()
|
||||
log_event('system', 'service.stopped')
|
||||
await asyncio.to_thread(shutdown_logging)
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
@@ -64,6 +74,32 @@ app.include_router(media_router)
|
||||
app.include_router(local_model_router)
|
||||
app.include_router(usage_router)
|
||||
app.include_router(provider_preview_router)
|
||||
app.include_router(log_router)
|
||||
|
||||
|
||||
@app.middleware('http')
|
||||
async def operation_log(request, call_next):
|
||||
token = request_id.set(uuid4().hex)
|
||||
started = perf_counter()
|
||||
status = 500
|
||||
failure = None
|
||||
try:
|
||||
response = await call_next(request)
|
||||
status = response.status_code
|
||||
response.headers['X-Request-ID'] = request_id.get()
|
||||
return response
|
||||
except Exception as exc:
|
||||
failure = exc
|
||||
raise
|
||||
finally:
|
||||
# Do not record query strings, request/response bodies or arbitrary URLs.
|
||||
route = getattr(request.scope.get('route'), 'path', 'unmatched')
|
||||
if not route.startswith('/api/logs') and (request.method not in {'GET', 'HEAD', 'OPTIONS'} or status >= 400 or perf_counter() - started > 1):
|
||||
log_event('http', 'request.finished', level='ERROR' if status >= 500 else 'WARNING' if status >= 400 else 'INFO',
|
||||
error=failure, method=request.method, route=route, status=status,
|
||||
duration_ms=round((perf_counter() - started) * 1000, 2),
|
||||
**{k: v for k, v in request.path_params.items() if k in {'run_id', 'task_id', 'note_id', 'job_id', 'provider_id'}})
|
||||
request_id.reset(token)
|
||||
|
||||
|
||||
@app.get("/health", response_model=HealthResponse, tags=["System"])
|
||||
|
||||
Reference in New Issue
Block a user