error.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import os
  2. import re
  3. import time
  4. from datetime import datetime
  5. from pathlib import Path
  6. from typing import List, Dict, Optional
  7. from fastapi import APIRouter, HTTPException
  8. from pydantic import BaseModel
  9. router = APIRouter()
  10. # 获取项目根目录
  11. project_root = Path(__file__).parent.parent.resolve()
  12. # 构建日志目录路径
  13. log_dir = project_root / "log"
  14. log_dir.mkdir(parents=True, exist_ok=True)
  15. ERROR_LOG_FILE = log_dir / "errors.log"
  16. class ErrorDetailResponse(BaseModel):
  17. id: str
  18. timestamp: str
  19. level: str
  20. message: str
  21. traceback: List[str] = []
  22. related_entries: List[Dict] = []
  23. def find_error_by_id(error_id: str) -> Optional[Dict]:
  24. """在错误日志文件中查找特定错误ID的条目"""
  25. if not ERROR_LOG_FILE.exists():
  26. return None
  27. # 日志格式: [时间戳] [级别] [模块] - 消息
  28. 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>.*)"
  29. with open(ERROR_LOG_FILE, 'r', encoding='utf-8') as f:
  30. for line in f:
  31. # 如果错误ID直接出现在行中
  32. if error_id in line:
  33. # 尝试匹配标准格式
  34. match = re.search(pattern, line)
  35. if match:
  36. return {
  37. "raw": line.strip(),
  38. "timestamp": match.group("timestamp"),
  39. "level": match.group("level"),
  40. "id": error_id,
  41. "full_line": line.strip(),
  42. "message": match.group("message")
  43. }
  44. else:
  45. # 如果标准格式匹配失败,使用回退方法
  46. # 尝试提取时间戳和级别
  47. ts_match = re.search(r"$$(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})$$", line)
  48. level_match = re.search(r"$$(\w+)$$", line)
  49. return {
  50. "raw": line.strip(),
  51. "timestamp": ts_match.group(1) if ts_match else "Unknown",
  52. "level": level_match.group(1) if level_match else "UNKNOWN",
  53. "id": error_id,
  54. "full_line": line.strip(),
  55. "message": line.strip()
  56. }
  57. return None
  58. def extract_traceback(log_file: Path, error_id: str) -> List[str]:
  59. """提取错误相关的堆栈跟踪信息"""
  60. traceback_lines = []
  61. if not log_file.exists():
  62. return traceback_lines
  63. try:
  64. # 读取整个日志文件
  65. with open(log_file, 'r', encoding='utf-8') as f:
  66. lines = f.readlines()
  67. # 查找错误ID所在的行
  68. error_line_index = -1
  69. for i, line in enumerate(lines):
  70. if error_id in line:
  71. error_line_index = i
  72. break
  73. if error_line_index == -1:
  74. return traceback_lines
  75. # 向前查找堆栈跟踪开始
  76. start_index = error_line_index
  77. while start_index > 0:
  78. if "Traceback (most recent call last):" in lines[start_index]:
  79. break
  80. start_index -= 1
  81. # 向后查找堆栈跟踪结束
  82. end_index = error_line_index
  83. while end_index < len(lines) - 1:
  84. # 检查下一行是否是新日志条目开始
  85. next_line = lines[end_index + 1]
  86. if re.match(r"$$\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$$", next_line):
  87. break
  88. end_index += 1
  89. # 提取堆栈跟踪
  90. if start_index <= end_index:
  91. traceback_lines = [line.strip() for line in lines[start_index:end_index + 1]]
  92. else:
  93. traceback_lines = []
  94. except Exception as e:
  95. print(f"提取堆栈跟踪失败: {str(e)}")
  96. return traceback_lines
  97. @router.get("/{error_id}", response_model=ErrorDetailResponse)
  98. async def get_error_details(error_id: str):
  99. """根据错误ID查询完整错误信息"""
  100. print(f"正在查询错误ID: {error_id}")
  101. print(f"日志文件路径: {ERROR_LOG_FILE}")
  102. print(f"文件存在: {ERROR_LOG_FILE.exists()}")
  103. # 检查日志文件是否存在
  104. if not ERROR_LOG_FILE.exists():
  105. print(f"错误日志文件不存在: {ERROR_LOG_FILE}")
  106. raise HTTPException(status_code=500, detail="错误日志文件不存在")
  107. # 1. 查找主错误条目
  108. error_entry = find_error_by_id(error_id)
  109. if not error_entry:
  110. print(f"未找到错误ID: {error_id} 的日志条目")
  111. # 尝试直接搜索文件内容
  112. found = False
  113. try:
  114. with open(ERROR_LOG_FILE, 'r', encoding='utf-8') as f:
  115. for i, line in enumerate(f):
  116. if error_id in line:
  117. print(f"在行 {i} 找到包含错误ID的行: {line.strip()}")
  118. found = True
  119. if not found:
  120. print("在日志文件中未找到包含错误ID的行")
  121. except Exception as e:
  122. print(f"读取日志文件失败: {str(e)}")
  123. raise HTTPException(status_code=404, detail="未找到指定的错误ID")
  124. # 2. 提取堆栈跟踪信息
  125. traceback_lines = extract_traceback(ERROR_LOG_FILE, error_id)
  126. # 如果没有找到堆栈跟踪,使用错误条目作为回退
  127. if not traceback_lines:
  128. traceback_lines = [error_entry["full_line"]]
  129. # 3. 解析错误消息
  130. # 如果已经有提取的消息,使用它
  131. if "message" in error_entry:
  132. message = error_entry["message"]
  133. else:
  134. # 否则尝试从完整行中提取消息
  135. message = error_entry["full_line"]
  136. # 尝试移除前面的时间戳和级别
  137. if " - " in message:
  138. message = message.split(" - ", 1)[-1]
  139. return ErrorDetailResponse(
  140. id=error_id,
  141. timestamp=error_entry["timestamp"],
  142. level=error_entry["level"],
  143. message=message,
  144. traceback=traceback_lines,
  145. related_entries=[]
  146. )