"""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, ) )