跳到主要内容
版本:0.6

导出到 OTLP 后端

cubepi.tracing.Tracer 接受任何 opentelemetry.sdk.trace.export.SpanExporter, 因此 OpenTelemetry 生态中的所有 exporter 均可直接使用。选择传输协议 (HTTP 或 gRPC),指向你的 collector,将 exporter 传入 Tracer 即可。

HTTP(OTLP/HTTP)

pip install "cubepi[tracing]" opentelemetry-exporter-otlp-proto-http
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
OTLPSpanExporter,
)
from cubepi.tracing import Tracer

tracer = Tracer(
service_name="my-bot",
service_version="1.4.2",
deployment_environment="prod",
agent_name="assistant",
exporters=[
OTLPSpanExporter(
endpoint="http://otel-collector:4318/v1/traces",
headers={"x-api-key": "…"}, # 后端专属配置
),
],
)

service_nameservice_versiondeployment_environmentagent_name 会作为 OTel Resource 属性(service.*gen_ai.agent.namedeployment.environment.name)透传,后端无需额外配置即可对运行进行分组。

gRPC(OTLP/gRPC)

pip install "cubepi[tracing]" opentelemetry-exporter-otlp-proto-grpc
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
OTLPSpanExporter,
)

exporter = OTLPSpanExporter(endpoint="otel-collector:4317", insecure=True)
tracer = Tracer(service_name="my-bot", exporters=[exporter])

后端配置示例

以下后端均消费 OTLP,区别仅在于 endpoint 和 auth header。

Jaeger(>=1.50)

Jaeger 原生支持 OTLP/HTTP,端口 4318:

OTLPSpanExporter(endpoint="http://jaeger:4318/v1/traces")

Grafana Tempo

发送到你的 collector,或直接发送到 Tempo 的 OTLP endpoint:

OTLPSpanExporter(endpoint="http://tempo:4318/v1/traces")

Honeycomb

OTLPSpanExporter(
endpoint="https://api.honeycomb.io/v1/traces",
headers={"x-honeycomb-team": HONEYCOMB_API_KEY},
)

Datadog(经由 OTel collector)

在 collector 中配置 Datadog exporter,再将数据发送给它:

OTLPSpanExporter(endpoint="http://otel-collector:4318/v1/traces")

Datadog 也直接支持原生 OTLP HTTP——格式相同,URL 不同。

AWS X-Ray(经由 collector)

OTel collector 内置 AWS X-Ray exporter;与其他 OTLP 目标的配置方式相同。

续接上游 trace

Tracer.attach(agent) 目前会让每次 agent 运行从自己的 trace 开始—— 暂无公开 API 可传入入站 traceparent,使 span 嵌套到调用方 HTTP handler 的 trace 中。内部钩子(Tracer._make_parent_context)为未来的 run_scope 特性预留;在该特性发布之前,agent 运行与周边服务 trace 仅通过 resource 属性(service.namegen_ai.agent.namecubepi.run_id)关联。

如果需要立即将上游 trace 延续到 CubePi,可以在调用 agent.prompt(...) 之前 手动设置 OTel 当前 span,让 agent 的 span 通过 OTel 的环境上下文继承它—— CubePi 不会覆盖已有的活跃父节点。

出站方向,MCP tools/call 会自动将 W3C traceparent 注入 HTTP 头, 让下游已埋点的 MCP 服务器能够续接 trace 并写入其自己的后端。

组合多个 exporter

可以传入多个 exporter,它们将接收所有 span。常见模式——JSONL 用于本地调试, OTLP 用于生产后端:

tracer = Tracer(
service_name="my-bot",
exporters=[
JsonlSpanExporter(directory="./cubepi-traces"),
OTLPSpanExporter(endpoint="https://api.honeycomb.io/v1/traces", headers={}),
],
)

Flush

Tracer 底层使用 BatchSpanProcessor,span 在后台异步导出。推荐使用 async with 形式,它会自动走完所有清理路径:

async with Tracer(...) as tracer, tracer.attached(agent):
await agent.prompt("...")
# 退出时:
# - detach() 同步执行:关闭所有已取消运行遗留的 span,
# 然后将 flush 调度为 asyncio.Task 并等待完成。
# - tracer.shutdown() 再次 flush(幂等)并关闭 exporter。

如果你保存了手动 tracer.attach(agent) 返回的 detach,它会返回已调度 的 flush Task。两种有效的手动模式:

# (a) 双保险——最安全,两者都执行。
finally:
detach() # 关闭已取消运行的 span,调度 flush
await tracer.shutdown() # 等待 force_flush,然后关闭 SDK

# (b) 等待 detach——不同时关闭时的单次调用。
finally:
await detach() # 等待已调度的 flush

在非 async 上下文(无正在运行的 event loop)中,detach() 返回 None—— 同步部分已执行,但 flush 需由调用方通过 await tracer.shutdown() 完成。

即使以上步骤都遗漏了——例如脚本在到达 finally 之前就抛出异常—— Tracer(atexit_flush=True)(默认)会注册进程退出处理程序,通过 BatchSpanProcessor 同步 flush 缓冲 span。SIGKILL / os._exit 时不运行; 如需保证必达,请使用 SimpleSpanProcessor(每个 span 同步导出)。