feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本
包含多个会话的累积代码变更: - backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔 - admin-web: ETL 状态页、任务管理、调度配置、登录优化 - miniprogram: 看板页面、聊天集成、UI 组件、导航更新 - etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强 - tenant-admin: 项目初始化 - db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8) - packages/shared: 枚举和工具函数更新 - tools: 数据库工具、报表生成、健康检查 - docs: PRD/架构/部署/合约文档更新 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
117
apps/backend/app/trace/error_handler.py
Normal file
117
apps/backend/app/trace/error_handler.py
Normal file
@@ -0,0 +1,117 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
异常/错误全链路追踪 — ERROR span 记录辅助函数
|
||||
|
||||
集成到全局异常处理器(http_exception_handler / unhandled_exception_handler),
|
||||
为 HTTPException 和未捕获异常分别记录 ERROR span。
|
||||
|
||||
- HTTPException → ERROR span(异常类型、status_code、detail、发生层级)
|
||||
- 未捕获异常 → ERROR span(异常类型、消息、堆栈摘要前 5 行)
|
||||
- 数据库异常(psycopg2.Error)→ DB_ERROR span(错误码、消息、触发 SQL)
|
||||
|
||||
所有函数均为安全调用:trace 未激活时静默跳过,不影响请求处理。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
|
||||
from starlette.exceptions import HTTPException
|
||||
|
||||
from app.trace.context import SpanType, TraceSpan, get_current_trace
|
||||
|
||||
|
||||
def record_http_exception(exc: HTTPException) -> None:
|
||||
"""为 HTTPException 记录 ERROR span。
|
||||
|
||||
记录内容:异常类型、status_code、detail、发生层级(exception_handler)。
|
||||
trace 未激活时静默跳过。
|
||||
"""
|
||||
ctx = get_current_trace()
|
||||
if ctx is None:
|
||||
return
|
||||
|
||||
ctx.add_span(TraceSpan(
|
||||
span_type=SpanType.ERROR,
|
||||
module="trace.error_handler",
|
||||
function="record_http_exception",
|
||||
description_zh=f"HTTP 异常: {exc.status_code} {exc.detail}",
|
||||
description_en=f"HTTP exception: {exc.status_code} {exc.detail}",
|
||||
params={},
|
||||
result_summary=f"{exc.status_code}",
|
||||
duration_ms=0.0,
|
||||
timestamp=datetime.now().isoformat(),
|
||||
extra={
|
||||
"exception_type": type(exc).__name__,
|
||||
"status_code": exc.status_code,
|
||||
"detail": str(exc.detail) if exc.detail else "",
|
||||
"layer": "exception_handler",
|
||||
},
|
||||
))
|
||||
|
||||
|
||||
def record_unhandled_exception(exc: Exception) -> None:
|
||||
"""为未捕获异常记录 ERROR span。
|
||||
|
||||
记录内容:异常类型、消息、堆栈摘要(最后 5 行)。
|
||||
trace 未激活时静默跳过。
|
||||
"""
|
||||
ctx = get_current_trace()
|
||||
if ctx is None:
|
||||
return
|
||||
|
||||
# 堆栈摘要:取最后 5 行,避免 span 过大
|
||||
tb_lines = traceback.format_exception(type(exc), exc, exc.__traceback__)
|
||||
stack_summary = "".join(tb_lines[-5:]) if len(tb_lines) > 5 else "".join(tb_lines)
|
||||
|
||||
ctx.add_span(TraceSpan(
|
||||
span_type=SpanType.ERROR,
|
||||
module="trace.error_handler",
|
||||
function="record_unhandled_exception",
|
||||
description_zh=f"未捕获异常: {type(exc).__name__}: {exc}",
|
||||
description_en=f"Unhandled exception: {type(exc).__name__}: {exc}",
|
||||
params={},
|
||||
result_summary=type(exc).__name__,
|
||||
duration_ms=0.0,
|
||||
timestamp=datetime.now().isoformat(),
|
||||
extra={
|
||||
"exception_type": type(exc).__name__,
|
||||
"message": str(exc),
|
||||
"stack_summary": stack_summary,
|
||||
"layer": "exception_handler",
|
||||
},
|
||||
))
|
||||
|
||||
|
||||
def record_db_exception(exc: Exception, sql: str = "") -> None:
|
||||
"""为数据库异常记录 DB_ERROR span。
|
||||
|
||||
记录内容:PostgreSQL 错误码(如有)、消息、触发 SQL。
|
||||
trace 未激活时静默跳过。
|
||||
"""
|
||||
ctx = get_current_trace()
|
||||
if ctx is None:
|
||||
return
|
||||
|
||||
# 尝试提取 psycopg2 错误码
|
||||
pgcode = getattr(exc, "pgcode", None) or ""
|
||||
|
||||
ctx.add_span(TraceSpan(
|
||||
span_type=SpanType.DB_ERROR,
|
||||
module="trace.error_handler",
|
||||
function="record_db_exception",
|
||||
description_zh=f"数据库异常: {type(exc).__name__}: {exc}",
|
||||
description_en=f"Database exception: {type(exc).__name__}: {exc}",
|
||||
params={},
|
||||
result_summary=type(exc).__name__,
|
||||
duration_ms=0.0,
|
||||
timestamp=datetime.now().isoformat(),
|
||||
extra={
|
||||
"exception_type": type(exc).__name__,
|
||||
"message": str(exc),
|
||||
"pgcode": pgcode,
|
||||
"sql": sql,
|
||||
"layer": "database",
|
||||
},
|
||||
))
|
||||
Reference in New Issue
Block a user