本次提交包含多项代码优化与规范修正: 1. 文档与注释优化:修正注释术语、补充注解与FR编号 2. 代码格式调整:统一空格、换行与缩进规范 3. 类型与接口完善:补充__all__导出、修正返回类型注解 4. 错误处理增强:新增领域错误类与校验逻辑 5. 依赖与导入调整:修复路径引用、统一时区导入 6. 协议与契约更新:完善接口文档与一致性注解
154 lines
5.5 KiB
Python
154 lines
5.5 KiB
Python
"""OpenTelemetryTracerAdapter:实现 TracerPort,使用内存 Span 树。
|
||
|
||
- 使用 SpanId / TraceId 值对象构造 Span 与 TraceContext
|
||
- 通过 ContextVar 隔离当前 Span 上下文(请求局部性)
|
||
- 追踪故障降级不阻断主流程
|
||
|
||
依赖边界:只依赖 yuxi.channels.contract(端口 + DTO)、标准库。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import uuid
|
||
from contextvars import ContextVar
|
||
|
||
from yuxi.channels.contract.dtos.option import Nothing, Option, Some
|
||
from yuxi.channels.contract.dtos.trace import (
|
||
Span,
|
||
SpanId,
|
||
SpanStatus,
|
||
TraceContext,
|
||
TraceId,
|
||
)
|
||
from yuxi.channels.contract.ports.driven.logger_port import LoggerPort
|
||
from yuxi.channels.contract.ports.driven.tracer_port import TracerPort
|
||
from yuxi.utils.datetime_utils import utc_now_naive
|
||
from yuxi.utils.trace_context import get_trace_id, set_trace_id
|
||
|
||
__all__ = ["OpenTelemetryTracerAdapter"]
|
||
|
||
# 当前 Span 上下文(请求局部,通过 ContextVar 隔离)
|
||
_current_span: ContextVar[Span | None] = ContextVar("yuxi_channel_current_span", default=None)
|
||
|
||
|
||
class OpenTelemetryTracerAdapter(TracerPort):
|
||
"""追踪被驱动适配器。
|
||
|
||
使用内存 Span 树与 ``ContextVar`` 实现请求局部的追踪上下文隔离。
|
||
``startSpan`` 构造 ``SpanId`` / ``TraceId`` 值对象并设置当前 Span 上下文;
|
||
``endSpan`` 清除上下文;``getCurrentTrace`` 返回当前 Span 的
|
||
``TraceContext``。故障时降级返回 fallback Span 或 None,不阻断主流程,
|
||
但通过 ``logger`` 记录故障以便运维感知。
|
||
"""
|
||
|
||
def __init__(self, logger: LoggerPort) -> None:
|
||
"""初始化追踪适配器。
|
||
|
||
Args:
|
||
logger: 日志端口,用于记录追踪故障(避免静默吞异常)。
|
||
"""
|
||
self._logger = logger
|
||
|
||
async def startSpan(
|
||
self,
|
||
name: str,
|
||
trace_id: str | None = None,
|
||
parent_span_id: str | None = None,
|
||
) -> Span:
|
||
"""启动 Span,设置当前 Span 上下文。
|
||
|
||
``trace_id`` 未提供时生成新追踪 ID。``parent_span_id`` 未提供时为
|
||
根 Span。故障时返回 fallback Span(不抛异常)。
|
||
|
||
Args:
|
||
name: Span 名称(操作标识)。
|
||
trace_id: 追踪 ID(可选,未提供时生成新 ID)。
|
||
parent_span_id: 父 Span ID(可选,根 Span 为 None)。
|
||
|
||
Returns:
|
||
Span 值对象。
|
||
"""
|
||
try:
|
||
# 优先使用调用方传入的 trace_id;未传入时尝试从共享 ContextVar
|
||
# 获取(由 TraceIdMiddleware 设置),避免覆盖已有 trace_id(P1-5/P1-6)
|
||
if trace_id is None:
|
||
trace_id = get_trace_id()
|
||
tid = TraceId(trace_id or uuid.uuid4().hex)
|
||
sid = SpanId(uuid.uuid4().hex)
|
||
parent = SpanId(parent_span_id) if parent_span_id else None
|
||
span = Span(
|
||
span_id=sid,
|
||
trace_id=tid,
|
||
parent_span_id=parent,
|
||
name=name,
|
||
started_at=utc_now_naive(),
|
||
)
|
||
_current_span.set(span)
|
||
# 同步 trace_id 到共享 ContextVar,使 channels 层异常构造时
|
||
# 能从 Error 基类自动关联 trace_id(P1-5/P1-6)
|
||
set_trace_id(tid.value)
|
||
return span
|
||
except Exception as e:
|
||
# 降级:返回 fallback Span,不抛异常,但记录故障以便运维感知
|
||
await self._logger.warn(
|
||
"tracer startSpan failed, returning fallback span",
|
||
error=str(e),
|
||
name=name,
|
||
trace_id=trace_id,
|
||
parent_span_id=parent_span_id,
|
||
)
|
||
tid = TraceId(uuid.uuid4().hex)
|
||
sid = SpanId(uuid.uuid4().hex)
|
||
span = Span(
|
||
span_id=sid,
|
||
trace_id=tid,
|
||
parent_span_id=None,
|
||
name=name,
|
||
started_at=utc_now_naive(),
|
||
)
|
||
_current_span.set(span)
|
||
set_trace_id(tid.value)
|
||
return span
|
||
|
||
async def endSpan(self, span: Span, status: str = "ok") -> None:
|
||
"""结束 Span,记录结束状态,清除当前 Span 上下文。
|
||
|
||
Args:
|
||
span: 已启动的 Span。
|
||
status: Span 状态(ok | error | unset),对齐 SpanStatus 枚举值。
|
||
"""
|
||
try:
|
||
span.status = SpanStatus(status)
|
||
span.ended_at = utc_now_naive()
|
||
except Exception as e:
|
||
# 降级:状态/结束时间记录失败不阻断主流程,但记录故障
|
||
await self._logger.warn(
|
||
"tracer endSpan status update failed",
|
||
error=str(e),
|
||
span_name=span.name,
|
||
status=status,
|
||
)
|
||
# ContextVar.set 不会抛异常,无需 try/except
|
||
_current_span.set(None)
|
||
|
||
async def getCurrentTrace(self) -> Option[TraceContext]:
|
||
"""查询当前追踪上下文。
|
||
|
||
返回当前 Span 的 ``TraceContext``,无当前 Span 时返回 ``Nothing``。
|
||
|
||
Returns:
|
||
``Some[TraceContext]``(存在当前 Span 时)或 ``Nothing``
|
||
(无当前 Span 时)。
|
||
"""
|
||
span = _current_span.get()
|
||
if span is None:
|
||
return Nothing()
|
||
return Some(
|
||
TraceContext(
|
||
trace_id=span.trace_id,
|
||
span_id=span.span_id,
|
||
parent_span_id=span.parent_span_id,
|
||
sampled=False,
|
||
)
|
||
)
|