import os import re import time from datetime import datetime from pathlib import Path from typing import List, Dict, Optional from fastapi import APIRouter, HTTPException from pydantic import BaseModel router = APIRouter() # 获取项目根目录 project_root = Path(__file__).parent.parent.resolve() # 构建日志目录路径 log_dir = project_root / "log" log_dir.mkdir(parents=True, exist_ok=True) ERROR_LOG_FILE = log_dir / "errors.log" class ErrorDetailResponse(BaseModel): id: str timestamp: str level: str message: str traceback: List[str] = [] related_entries: List[Dict] = [] def find_error_by_id(error_id: str) -> Optional[Dict]: """在错误日志文件中查找特定错误ID的条目""" if not ERROR_LOG_FILE.exists(): return None # 日志格式: [时间戳] [级别] [模块] - 消息 pattern = r"$$(?P\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})$$\s+$$(?P\w+)$$\s+$$.*?$$\s+-\s+(?P.*)" with open(ERROR_LOG_FILE, 'r', encoding='utf-8') as f: for line in f: # 如果错误ID直接出现在行中 if error_id in line: # 尝试匹配标准格式 match = re.search(pattern, line) if match: return { "raw": line.strip(), "timestamp": match.group("timestamp"), "level": match.group("level"), "id": error_id, "full_line": line.strip(), "message": match.group("message") } else: # 如果标准格式匹配失败,使用回退方法 # 尝试提取时间戳和级别 ts_match = re.search(r"$$(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})$$", line) level_match = re.search(r"$$(\w+)$$", line) return { "raw": line.strip(), "timestamp": ts_match.group(1) if ts_match else "Unknown", "level": level_match.group(1) if level_match else "UNKNOWN", "id": error_id, "full_line": line.strip(), "message": line.strip() } return None def extract_traceback(log_file: Path, error_id: str) -> List[str]: """提取错误相关的堆栈跟踪信息""" traceback_lines = [] if not log_file.exists(): return traceback_lines try: # 读取整个日志文件 with open(log_file, 'r', encoding='utf-8') as f: lines = f.readlines() # 查找错误ID所在的行 error_line_index = -1 for i, line in enumerate(lines): if error_id in line: error_line_index = i break if error_line_index == -1: return traceback_lines # 向前查找堆栈跟踪开始 start_index = error_line_index while start_index > 0: if "Traceback (most recent call last):" in lines[start_index]: break start_index -= 1 # 向后查找堆栈跟踪结束 end_index = error_line_index while end_index < len(lines) - 1: # 检查下一行是否是新日志条目开始 next_line = lines[end_index + 1] if re.match(r"$$\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$$", next_line): break end_index += 1 # 提取堆栈跟踪 if start_index <= end_index: traceback_lines = [line.strip() for line in lines[start_index:end_index + 1]] else: traceback_lines = [] except Exception as e: print(f"提取堆栈跟踪失败: {str(e)}") return traceback_lines @router.get("/{error_id}", response_model=ErrorDetailResponse) async def get_error_details(error_id: str): """根据错误ID查询完整错误信息""" print(f"正在查询错误ID: {error_id}") print(f"日志文件路径: {ERROR_LOG_FILE}") print(f"文件存在: {ERROR_LOG_FILE.exists()}") # 检查日志文件是否存在 if not ERROR_LOG_FILE.exists(): print(f"错误日志文件不存在: {ERROR_LOG_FILE}") raise HTTPException(status_code=500, detail="错误日志文件不存在") # 1. 查找主错误条目 error_entry = find_error_by_id(error_id) if not error_entry: print(f"未找到错误ID: {error_id} 的日志条目") # 尝试直接搜索文件内容 found = False try: with open(ERROR_LOG_FILE, 'r', encoding='utf-8') as f: for i, line in enumerate(f): if error_id in line: print(f"在行 {i} 找到包含错误ID的行: {line.strip()}") found = True if not found: print("在日志文件中未找到包含错误ID的行") except Exception as e: print(f"读取日志文件失败: {str(e)}") raise HTTPException(status_code=404, detail="未找到指定的错误ID") # 2. 提取堆栈跟踪信息 traceback_lines = extract_traceback(ERROR_LOG_FILE, error_id) # 如果没有找到堆栈跟踪,使用错误条目作为回退 if not traceback_lines: traceback_lines = [error_entry["full_line"]] # 3. 解析错误消息 # 如果已经有提取的消息,使用它 if "message" in error_entry: message = error_entry["message"] else: # 否则尝试从完整行中提取消息 message = error_entry["full_line"] # 尝试移除前面的时间戳和级别 if " - " in message: message = message.split(" - ", 1)[-1] return ErrorDetailResponse( id=error_id, timestamp=error_entry["timestamp"], level=error_entry["level"], message=message, traceback=traceback_lines, related_entries=[] )