# ============================================================================= # 企微IT智能服务台 — 运行期日志 API (standard SOP 第三阶段) # ============================================================================= # 说明:管理后台查看后端运行期日志 # GET /admin/runtime-logs 正常查询(分页 + 级别/时间/关键字筛选) # GET /admin/runtime-logs?download=true 下载命中行(text/plain 附件) # 权限:require_admin(非 admin 返回 403,由依赖装饰器强制) # ============================================================================= import logging from datetime import datetime from typing import Optional from fastapi import APIRouter, Depends, Query from fastapi.responses import StreamingResponse from app.dependencies import require_admin, get_current_user, UserInfo from app.services.runtime_log_service import ( iter_runtime_log_lines, query_runtime_logs, ) from app.utils.response import success_response logger = logging.getLogger(__name__) router = APIRouter(prefix="/admin/runtime-logs", tags=["运行期日志"]) @router.get("") @require_admin async def get_runtime_logs( level: str = Query("INFO", description="日志级别阈值(DEBUG/INFO/WARNING/ERROR/CRITICAL),返回 >= 该级别"), from_time: Optional[datetime] = Query(None, alias="from", description="起始时间(ISO8601)"), to_time: Optional[datetime] = Query(None, alias="to", description="结束时间(ISO8601)"), keyword: Optional[str] = Query(None, description="按消息关键字子串筛选(大小写不敏感)"), page: int = Query(1, ge=1, description="页码"), page_size: int = Query(50, ge=1, le=500, description="每页条数"), download: bool = Query(False, description="true 时返回命中行文本下载"), current_user: UserInfo = Depends(get_current_user), ): """查询后端运行期日志(分页 + 级别/时间/关键字筛选)。 权限:仅 admin 角色可访问,非 admin 由 require_admin 装饰器返回 403。 """ # 下载模式:流式返回命中行文本,触发浏览器文件下载 if download: ts = datetime.now().strftime("%Y%m%d-%H%M%S") headers = { "Content-Disposition": f'attachment; filename="runtime-logs-{ts}.log"' } return StreamingResponse( iter_runtime_log_lines( level=level, from_time=from_time, to_time=to_time, keyword=keyword, ), media_type="text/plain; charset=utf-8", headers=headers, ) # 普通查询:分页返回结构化条目 result = await query_runtime_logs( level=level, from_time=from_time, to_time=to_time, keyword=keyword, page=page, page_size=page_size, ) return success_response(data=result)