ForcePilot/backend/package/yuxi/channels/adapters/opentelemetry_tracer_adapter.py
Kris b88c0ae29e feat(channels): 批量新增多渠道网关限界上下文基础代码与契约
新增完整的 channels 限界上下文模块,包含契约层、领域核心层、应用服务、管道编排、插件体系、基础设施组合根等全层级代码,新增飞书与微信 iLink 渠道插件基础结构,补充各类 DTO、端口协议与领域服务实现。
2026-07-02 03:22:12 +08:00

168 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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_idP1-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_idP1-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()