from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.ext.asyncio import AsyncSession from pydantic import BaseModel from typing import Any, Optional from app.database import get_db from app.models.user import User from app.routers.auth import get_current_user from app.services.log_service import LogService, parse_datetime_filter, serialize_log router = APIRouter(prefix="/api/logs", tags=["Log"]) class LogOut(BaseModel): id: str level: str type: str user_id: Optional[str] request_id: Optional[str] route: Optional[str] method: Optional[str] status_code: Optional[int] error_type: Optional[str] operation: Optional[str] message: str source: Optional[str] details: Optional[dict[str, Any]] duration_ms: Optional[int] created_at: Optional[str] updated_at: Optional[str] class LogStatsOut(BaseModel): total: int by_type: dict by_level: dict class LogQueryOut(BaseModel): logs: list[LogOut] total: int page: int page_size: int @router.get("", response_model=LogQueryOut) async def list_logs( log_type: Optional[str] = Query(None, description="日志类型: agent/system/chat"), level: Optional[str] = Query(None, description="日志级别: debug/info/warning/error"), source: Optional[str] = Query(None, description="来源模块"), request_id: Optional[str] = Query(None, description="请求 ID"), route: Optional[str] = Query(None, description="路由"), operation: Optional[str] = Query(None, description="业务操作"), status_code: Optional[int] = Query(None, description="HTTP 状态码"), start_at: Optional[str] = Query(None, description="开始时间 ISO"), end_at: Optional[str] = Query(None, description="结束时间 ISO"), page: int = Query(1, ge=1), page_size: int = Query(50, ge=1, le=200), current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """查询日志列表""" start_dt = parse_datetime_filter(start_at) end_dt = parse_datetime_filter(end_at) if start_dt and end_dt and start_dt > end_dt: raise HTTPException(status_code=422, detail="开始时间不能晚于结束时间") svc = LogService(db) offset = (page - 1) * page_size logs, total = await svc.list_logs( log_type=log_type, level=level, user_id=current_user.id, source=source, request_id=request_id, route=route, operation=operation, status_code=status_code, start_at=start_dt, end_at=end_dt, limit=page_size, offset=offset, ) return LogQueryOut( logs=[LogOut.model_validate(serialize_log(log)) for log in logs], total=total, page=page, page_size=page_size, ) @router.get("/stats", response_model=LogStatsOut) async def get_log_stats( log_type: Optional[str] = Query(None, description="日志类型: agent/system/chat"), level: Optional[str] = Query(None, description="日志级别: debug/info/warning/error"), source: Optional[str] = Query(None, description="来源模块"), request_id: Optional[str] = Query(None, description="请求 ID"), route: Optional[str] = Query(None, description="路由"), operation: Optional[str] = Query(None, description="业务操作"), status_code: Optional[int] = Query(None, description="HTTP 状态码"), start_at: Optional[str] = Query(None, description="开始时间 ISO"), end_at: Optional[str] = Query(None, description="结束时间 ISO"), current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """获取日志统计""" start_dt = parse_datetime_filter(start_at) end_dt = parse_datetime_filter(end_at) if start_dt and end_dt and start_dt > end_dt: raise HTTPException(status_code=422, detail="开始时间不能晚于结束时间") svc = LogService(db) stats = await svc.get_log_stats( log_type=log_type, level=level, user_id=current_user.id, source=source, request_id=request_id, route=route, operation=operation, status_code=status_code, start_at=start_dt, end_at=end_dt, ) return LogStatsOut(**stats) @router.get("/recent", response_model=list[LogOut]) async def get_recent_logs( log_type: Optional[str] = Query(None), hours: int = Query(24, ge=1, le=168), limit: int = Query(50, ge=1, le=200), current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """获取最近的日志""" svc = LogService(db) logs = await svc.get_recent_logs(log_type=log_type, user_id=current_user.id, hours=hours, limit=limit) return [LogOut.model_validate(serialize_log(log)) for log in logs]