Mastra 与 Hono 多服务分布式链路追踪实战:基于 OtelBridge 与 Arize Phoenix 打通三服务调用链
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
本指南以 Mastra 仓库中的hono-multi示例为蓝本,讲解如何用 Mastra 的OtelBridge将 Agent 产生的内部 Span 无缝接入 OpenTelemetry(OTEL)生态,让跨三个 Hono 服务的 HTTP 调用、Mastra Agent 运行与 LLM 调用共享同一条 trace ID,并在 Arize Phoenix 中可视化。读完本文,你将掌握 OtelBridge 的工作原理、共享 OTEL 插桩的搭建方式,以及一套可复制、可验证的多服务分布式追踪落地流程。
示例概览:一条贯穿三个服务的调用链
示例位于 observability/_examples/otel-bridge/hono-multi,包含三个独立服务与一个共享插桩包,整体调用拓扑如下:
service-one (port 3000) ↓ HTTP request service-two (port 3001) ↓ HTTP request service-mastra (port 4000) → Mastra agent with OtelBridge注意:README 中标注 service-mastra 端口为 4000,但实际源码(service-mastra/src/index.ts)中监听端口为3002,集成测试(src/integration.test.ts)也按 3000/3001/3002 三端口验证。部署时以实际源码为准。
所有服务统一使用 OpenTelemetry 插桩,trace 数据最终汇入 Arize Phoenix 可视化。其核心价值在于演示trace context 的正确传播:如果没有 OtelBridge,Mastra 会为 Agent 运行创建新的 trace ID,导致与其他服务的 trace 断链;接入后,Mastra 的 Span 与上游 HTTP Span 共享同一个 trace ID,形成完整的链路。
OtelBridge 原理:Mastra 可观测性与 OTEL 的桥梁
OtelBridge 是@mastra/otel-bridge包导出的核心类,其实现位于 observability/otel-bridge/src/bridge.ts。从源码结构看,它通过三种机制完成双向打通:
- 读取 trace context:从传入 HTTP 请求的
traceparent头中解析 W3C 上下文,让 Mastra 的根 Span 挂接到外部链路之下; - 创建真实 OTEL Span:当 Mastra 创建内部 Span(
createSpan)时,同步调用全局 TracerProvider 的startSpan,生成真正的 OTEL Span,而非 Mastra 内部私有的 Span; - 维护层级关系:通过
otelSpanMap缓存「Mastra spanId ↔ OTEL span/context」的映射,在 Span 结束时用 SpanConverter(来自@mastra/otel-exporter)将 Mastra 属性转换为符合 OTEL 语义约定的属性并end()该 Span。
值得注意的两个细节:
- 若当前进程没有注册 OTEL SDK,全局 Tracer 返回的 non-recording span 上下文无效(全零 ID),
createSpan会提前返回undefined,让 Mastra 回退到自有的 ID 生成逻辑,避免 ID 碰撞(见 bridge.ts#L229-L238); executeInContext/executeInContextSync会把函数放进已存储的 OTEL context 中执行,从而让 Mastra Span 内部的任何 OTEL 插桩代码(HTTP 客户端、数据库驱动等)都能以正确的父子关系挂到当前 Span 下。
在 service-mastra 中接入方式非常简洁(service-mastra/src/index.ts):
import { Mastra } from '@mastra/core'; import { Observability, SensitiveDataFilter } from '@mastra/observability'; import { OtelBridge } from '@mastra/otel-bridge'; export const mastra: Mastra = new Mastra({ observability: new Observability({ configs: { default: { serviceName: 'tracing-exp', spanOutputProcessors: [new SensitiveDataFilter()], bridge: new OtelBridge(), }, }, }), agents: { 'test-agent': testAgent }, });其中serviceName用于标识服务;SensitiveDataFilter会在 Span 输出前过滤敏感数据;bridge: new OtelBridge()即打通 OTEL 的关键配置。
环境准备与三步搭建
运行本示例需要以下环境:
- Docker:用于启动 Arize Phoenix(本地开源、免费的可观测性后端)
- Node.js 22.13.0 及以上
- pnpm >= 10(Monorepo 包管理)
- OpenAI API Key:service-mastra 通过
openai/gpt-4o-mini调用大模型
第一步:启动 Arize Phoenix
在示例根目录执行:
pnpm docker:up该命令读取 docker-compose.yml,拉起arizephoenix/phoenix:latest镜像:
- UI 地址:http://localhost:6006
- OTLP 接收端点:http://localhost:6006/v1/traces
- 同时暴露 4317 端口(可选的 gRPC OTLP 接收器)
第二步:配置 OpenAI API Key
在示例根目录创建.env文件:
echo "OPENAI_API_KEY=your-key-here" > .env第三步:安装依赖并构建
pnpm install pnpm buildpnpm build会依次构建共享插桩包@mastra/hono-multi-instrumentation和service-mastra(见根 package.json)。必须先构建再启动,因为 service-mastra 以 workspace 依赖引用共享插桩包。
启动三个服务
需要开启三个终端窗口分别启动服务:
终端 1 - service-one(端口 3000)
cd observability/_examples/otel-bridge/hono-multi/service-one pnpm start终端 2 - service-two(端口 3001)
cd observability/_examples/otel-bridge/hono-multi/service-two pnpm start终端 3 - service-mastra(端口 3002)
cd observability/_examples/otel-bridge/hono-multi/service-mastra pnpm start每个服务的start脚本都会先通过--import=./src/instrumentation.ts预加载遥测初始化(以 service-mastra 为例,见 service-mastra/package.json),确保telemetry 在任何 HTTP 请求处理之前完成初始化——这是 trace context 正确传播的前提之一。
验证链路:发一次请求看全链路
向入口服务发请求:
curl http://localhost:3000/service-one期望返回:
{ "message": "service-one → service-two → service-mastra (agent: Hello there friend!)" }调用链实际流向(对应各服务源码):service-one 的/service-one路由通过 service-two-client.ts 用fetch请求http://localhost:3001/service-two;service-two 再通过 service-mastra-client.ts 请求http://localhost:3002/service-mastra;最终 service-mastra 路由调用mastra.getAgent('test-agent')执行生成(见 service-mastra/src/index.ts#L42-L47),并把traceId一并返回。
在 Phoenix 中查看 Trace
- 打开 http://localhost:6006
- 应能看到一条包含三个服务全部 Span 的 trace,包含:
- service-one、service-two 的 HTTP Span
- service-mastra 的 Agent 运行 Span
- LLM generation Span
- 所有 Span 共享同一个 trace ID
架构细节与源码剖析
service-one:入口服务
- 使用 Hono +
@hono/otel的httpInstrumentationMiddleware做自动插桩(service-one/src/index.ts); - 通过
fetch调用下游(UndiciInstrumentation自动捕获该调用产生的 client span); - 响应中包含从下游逐层透传的
traceId。
service-two:中间服务
- 接收 service-one 请求并转发给 service-mastra,是验证HTTP trace context 传播的关键一跳;
- 同样使用
httpInstrumentationMiddleware插桩,中间不加任何额外处理,依靠 W3Ctraceparent头完成上下文延续(service-two/src/index.ts)。
service-mastra:Mastra + OtelBridge
- 通过
MastraServer(@mastra/hono的 HonoServerAdapter)注册 Agent 路由,并附加openapi.json、Swagger UI(service-mastra/src/index.ts#L33-L50); - Agent 定义见 service-mastra/src/agent.ts,使用
openai/gpt-4o-mini; - 关键点:
httpInstrumentationMiddleware()在 Mastra 路由注册之前通过app.use('*', ...)挂载,确保每个入站请求先进入 OTEL 上下文,再进入 Mastra 逻辑。
共享插桩包:统一的 OTEL 配置
所有服务共用@mastra/hono-multi-instrumentation(源码在 instrumentation/src/index.ts),它负责:
- 用
NodeSDK配置 resource(服务名默认tracing-exp,可用环境变量ARIZE_PROJECT_NAME覆盖); - 注册
HttpInstrumentation与UndiciInstrumentation自动插桩 HTTP 服务端与fetch客户端; - 使用
W3CTraceContextPropagator作为传播器(解析/注入traceparent头); - 通过
BatchSpanProcessor+ArizeOpenInferenceOTLPTraceExporter批量导出 trace 到 Phoenix(端点默认http://localhost:6006/v1/traces,可用OTEL_EXPORTER_OTLP_ENDPOINT覆盖)。
导出器源码在 instrumentation/src/arize-exporter.ts,它基于 OTLP proto exporter 扩展,额外做了两项工作:
- 将 Mastra 的
gen_ai.prompt/gen_ai.completion属性转换为 OpenInference 的gen_ai.input.messages/gen_ai.output.messages结构(convertMastraMessagesToGenAIMessages,best-effort 转换,失败时原样返回); - 将属性转为
gen_ai语义约定,供 Phoenix 以 AI 应用视图渲染; - 支持 Arize AX(云端)与 Phoenix(本地)两种模式:传入
spaceId时走 Arize AX 头与https://otlp.arize.com/v1/traces端点;只传apiKey时走标准Authorization: Bearer头。
用集成测试自动验证传播正确性
示例还提供了完整的集成测试(src/integration.test.ts),可自动化验证「三服务 + Phoenix」整条链路。其运行前提与 README 中的说明一致:
- Phoenix 已在运行:
pnpm docker:up - 已配置 OpenAI API Key(两种方式任选):
- 推荐:在示例根目录创建
.env文件; - 或运行测试前设置环境变量。
- 推荐:在示例根目录创建
- 已完成构建:
pnpm build
# 使用 .env 文件(推荐) pnpm test # 或使用内联环境变量 OPENAI_API_KEY=your-key pnpm test测试的行为(从源码可以确认):
- 模块加载时先探测 Phoenix 的
/graphql端点可用性,以及OPENAI_API_KEY是否存在;任一不满足则整组测试标记为 skip 并打印原因(integration.test.ts#L38-L47); beforeAll依次以子进程启动三个服务,等待日志中出现 "Server listening" 判定就绪;- 通过
http://localhost:3000/service-one发起请求,用正则"traceId":"([a-f0-9]{32})"从响应中提取 32 位 hex trace ID; - 通过 Phoenix 的 GraphQL
getTraceByOtelId查询该 trace,轮询等待(最长 15 秒); - 断言要点:
- 存在
mastra.span.type = agent_run的 Span; - 存在
model_generation的 LLM Span; - 所有 Span 共享唯一 trace ID(
traceIds.length === 1); agent_runSpan 有父 Span 且父 Span 存在于同一 trace 中;- LLM Span 的
parentId等于 Agent Span 的spanId(父子关系正确);
- 存在
afterAll按逆序优雅停止三个服务(SIGTERM,5 秒超时后强制 SIGKILL)。
常见问题排查
出现断链(trace ID 不一致)
这通常说明 trace context 传播失败,按顺序检查:
- 三个服务是否都使用了共享插桩包——检查各自
instrumentation.ts是否调用了startTelemetry(); - service-mastra 是否配置了 OtelBridge——缺失
bridge: new OtelBridge()时 Mastra 会生成新的 trace ID; - telemetry 是否在创建 Hono app 之前初始化——注意
start脚本中的--import=./src/instrumentation.ts预加载顺序,这是插桩能够捕获 HTTP 请求的前提。
Agent 调用报错
- 确认
.env(示例根目录)中存在OPENAI_API_KEY; - 确认 OpenAI API 网络可达;
- 确认账号余额/额度充足。
清理与关闭
# 停止 Phoenix pnpm docker:down # 停止三个服务:在每个终端按 Ctrl+C三个服务都实现了SIGTERM/SIGINT优雅退出:先关闭 HTTP server,再调用stopTelemetry()触发 SDKshutdown()冲刷剩余 Span,确保进程退出前数据完整导出(参考 service-one/src/index.ts#L37-L57 等实现)。
与上游示例的对比与启示
该示例基于社区的 Hono tracing 示例改造而来,核心改进有四方面:改用本地开源的 Arize Phoenix(替代云端 Arize);引入 OtelBridge 修复 trace 传播断链问题;通过 workspace 依赖融入 Mastra Monorepo 直接复用最新包;并最终用集成测试证明了「trace 能正确贯穿所有服务」这一修复成果。
对于在生产环境接入 Mastra 可观测性的开发者,本示例给出了一条清晰的落地路径:共享插桩包统一 OTEL 配置 → 中间服务靠 W3C 传播器自然透传 → 末端服务用 OtelBridge 把 Mastra 内部 Span 变为真实 OTEL Span → 集成测试保障链路不回归。这套模式同样适用于任何基于 OTEL 的后端(如 Jaeger、Tempo、OTLP Collector 等),只需替换导出器端点即可。
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考