# -*- 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", }, ))