123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- 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<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})$$\s+$$(?P<level>\w+)$$\s+$$.*?$$\s+-\s+(?P<message>.*)"
- 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=[]
- )
|