新增完整的 channels 限界上下文模块,包含契约层、领域核心层、应用服务、管道编排、插件体系、基础设施组合根等全层级代码,新增飞书与微信 iLink 渠道插件基础结构,补充各类 DTO、端口协议与领域服务实现。
168 lines
6.1 KiB
Python
168 lines
6.1 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
|
||
|
||
# 当前 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,
|
||
)
|
||
try:
|
||
_current_span.set(None)
|
||
except Exception as e:
|
||
# 降级:上下文清除失败不阻断主流程,但记录故障
|
||
await self._logger.warn(
|
||
"tracer endSpan current span clear failed",
|
||
error=str(e),
|
||
span_name=span.name,
|
||
)
|
||
|
||
async def getCurrentTrace(self) -> Option[TraceContext]:
|
||
"""查询当前追踪上下文。
|
||
|
||
返回当前 Span 的 ``TraceContext``,无当前 Span 时返回 ``Nothing``。
|
||
故障时返回 ``Nothing``(降级不阻断,遵守 INV-10)。
|
||
|
||
Returns:
|
||
``Some[TraceContext]``(存在当前 Span 时)或 ``Nothing``
|
||
(无当前 Span 或追踪故障降级时)。
|
||
"""
|
||
try:
|
||
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,
|
||
)
|
||
)
|
||
except Exception as e:
|
||
# 降级:返回 Nothing 不阻断主流程,但记录故障以便运维感知
|
||
await self._logger.warn(
|
||
"tracer getCurrentTrace failed, returning Nothing",
|
||
error=str(e),
|
||
)
|
||
return Nothing()
|