ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Mastra OpenTelemetry Bridge(@mastra/otel-bridge)完整指南:双向打通 Mastra 与 OTEL 可观测性

Mastra OpenTelemetry Bridge(@mastra/otel-bridge)完整指南:双向打通 Mastra 与 OTEL 可观测性 Mastra OpenTelemetry Bridgemastra/otel-bridge完整指南双向打通 Mastra 与 OTEL 可观测性【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastramastra/otel-bridge是 Mastra 可观测性体系中的 OpenTelemetry 桥接层它实现 Mastra 与 OTEL 基础设施的双向集成既能从 OTEL 的活跃 span 上下文AsyncLocalStorage读取 trace ID 与父 span ID也能为 Mastra 的每个 span 创建真实的 OTEL span从而维护正确的 trace 层级。读完本文你将掌握如何把 Mastra Agent 的追踪数据无缝汇入 OTEL 生态如 Jaeger、OTLP Collector、云厂商 APM让 Agent、工具调用、工作流步骤与数据库/HTTP 客户端调用落在同一条分布式 trace 里并理解桥接背后的 span 生命周期与上下文传播原理。一、为什么需要 OTEL Bridge双向集成的动机Mastra 自带一套完善的 observability 模型span/trace/log/metric/score/feedback但它内部的 span 并不是标准 OTEL span。如果你已经拥有 OTEL 基础设施自动注入 HTTP/DB 埋点的 SDK、OTLP Collector、统一 trace 后端会出现两个问题Mastra 的 span 进不了 OTEL 生态Mastra 产生的 trace 数据与 OTEL 采集的 trace 各自独立无法在同一个 trace 视图里聚合。上下文断裂Mastra 运行 Agent 时调用的外部代码数据库驱动、HTTP 客户端如果被 OTEL 自动埋点这些 span 无法正确挂到 Mastra span 之下导致 trace 树断裂。OtelBridge正是为解决这两个问题而设计。从源码注释bridge.ts和包入口index.ts可以看到它的双向职责OTEL → Mastra自动读取 OTEL 环境上下文AsyncLocalStorage继承活跃 OTEL span 的 trace ID 与父 span ID必要时从请求头提取 W3C trace 上下文Mastra → OTEL为每个 Mastra span 创建真实的 OTEL span维护父子关系并让 OTEL 自动埋点的代码HTTP、DB正确嵌套在 Mastra span 之下。桥的名称被定义为otel见 bridge.ts 中name otel测试 bridge.test.ts 也对此做了断言在 observability 配置中通过bridge字段挂载。二、安装与前置依赖2.1 安装npm install mastra/otel-bridge根据 package.json该包以 ESM 为主type: module同时通过exports同时提供importdist/index.js与requiredist/index.cjs两种入口Node.js 版本要求22.13.0。2.2 依赖与 peer 依赖包内部依赖mastra/observabilityworkspace 依赖提供BaseExporter与getExternalParentIdmastra/otel-exporterworkspace 依赖提供SpanConverter、convertLog、getSpanKind等转换工具opentelemetry/api^1.9.1与opentelemetry/api-logs^0.221.0。peer 依赖均为可选见peerDependenciesMetamastra/core1.16.0-0 2.0.0-0opentelemetry/auto-instrumentations-node0.50.0可选opentelemetry/sdk-node0.50.0可选。这里需要特别强调一个关键前提源码与测试都反复验证OtelBridge只有在 OTEL SDK如opentelemetry/sdk-node或opentelemetry/sdk-logs已注册全局 TracerProvider / LoggerProvider 时才能产出有效 span。若未注册OTEL API 会回退到 no-op tracerspan 的 ID 全是零值0000000000000000/00...00桥会检测到无效 span context 并返回undefined见下文错误处理让 Mastra 核心用自己的 ID 生成器兜底。因此生产使用请务必同时安装并初始化 OTEL SDK。三、最小接入把 OtelBridge 挂到 MastraOtelBridge通过Mastra构造函数的observability.configs配置注入bridge.ts 与包 README 中的示例一致import { OtelBridge } from mastra/otel-bridge; import { Mastra } from mastra/core; import { Observability } from mastra/observability; const mastra new Mastra({ agents: { myAgent }, observability: new Observability({ configs: { default: { serviceName: my-service, bridge: new OtelBridge(), }, }, }), });要点说明serviceName观测实例的服务名会被透传给桥用于 OTEL span 的资源属性与SpanConverter的格式化见 bridge.ts 的init方法bridge传入OtelBridge实例即可同一实例可被多个配置共享OtelBridge实现了ObservabilityBridge接口定义于 packages/core/src/observability/types/core.ts该接口要求实现name、createSpan、flush、shutdown并可选择性实现executeInContext/executeInContextSync/releaseSpan等。3.1 可选配置自定义 Tracer/Logger ProviderOtelBridge的构造函数接受OtelBridgeConfigbridge.tstype OtelBridgeConfig BaseExporterConfig { tracerProvider?: TracerProvider; // 默认取全局 otelTrace.getTracerProvider() loggerProvider?: LoggerProvider; // 默认取全局 otelLogs.getLoggerProvider() };默认情况下桥使用全局注册的 provider。当你不希望触碰全局 provider例如多租户场景或测试隔离时可以显式传入自定义 providerimport { BasicTracerProvider } from opentelemetry/sdk-trace-node; import { LoggerProvider, SimpleLogRecordProcessor } from opentelemetry/sdk-logs; const bridge new OtelBridge({ tracerProvider: customTracerProvider, loggerProvider: customLoggerProvider, });测试 bridge.test.ts 验证了自定义 provider 的行为自定义 provider 创建 span / 发射日志时不会触碰全局 provider且flush()只会冲刷传入的自定义 provider 而不是全局的。桥内部会为 provider 注册两个命名 instrumenttracer 名称mastra/otel-bridge版本1.0.0logger 名称mastra/otel-bridge版本1.0.0。见 bridge.ts。四、核心机制一createSpan —— 为 Mastra span 创建真实 OTEL spancreateSpan是桥最关键的方法在 Mastra 创建 span 时被调用用于获取桥生成的 IDbridge.ts。它完成以下步骤4.1 确定父上下文Parent Context父上下文的解析优先级如下活跃环境上下文默认取otelContext.active()即 AsyncLocalStorage 中的当前 OTEL 上下文外部父 span通过getExternalParentId(options)沿链向上查找非内部父 span该函数实现在 observability/mastra/src/spans/base.ts若命中桥的otelSpanMap中记录的 span则以其存储的 OTEL context 作为父上下文持久化恢复的 trace若 span 带有traceIdparentSpanId例如工作流 suspend/resume 后从持久化快照恢复且父 OTEL span 已不在 map 中父 span 可能在另一个进程中早已结束则用持久化 ID 构造一个isRemote: true的 span context 作为父级从而延续原 trace而不是开启一条新 trace。该逻辑配合TraceFlags.SAMPLED且会用isSpanContextValid校验 ID 合法性杜绝注入畸形 ID 造成垃圾 trace link。相关回归测试针对 issue #20771工作流恢复后应延续持久化 trace测试 bridge.test.ts 覆盖了父 span 已死时延续持久化 trace存在活父 span 时优先用活父畸形持久化 ID 回退到新 trace三个场景。4.2 创建 OTEL span 并确定 SpanKindconst otelSpan this.otelTracer.startSpan( options.name, { kind: getSpanKind(options.type), // SpanKind 在创建时必须确定不可更改 ...(options.startTime ? { startTime: options.startTime } : {}), }, parentOtelContext, );getSpanKind来自mastra/otel-exporter导出见 observability/otel-exporter/src/index.ts它将 Mastra 的SpanType如AGENT_RUN、WORKFLOW_RUN、TOOL_CALL、LLM映射为 OTEL 的SpanKind如INTERNAL、SERVER、CLIENT等。由于 SpanKind 是创建时不可变属性必须在startSpan时确定。startTime若存在也会一并传入保证时间线对齐。4.3 无效 span context 的兜底创建 span 后桥会检查otelSpan.spanContext()是否有效若无效说明没有注册 OTEL SDK全局 tracer 回退到 no-op桥会结束刚创建的 span并返回undefined让 Mastra 核心回退到自己的 ID 生成器bridge.ts这是针对 issue #15589 的回归修复此前桥会把全零 ID 返回给核心导致所有 span 共享同一 ID破坏 TrackingExporter 的父子匹配队列并引发 CPU 空转。对应测试见 bridge.test.tswhen no OTEL SDK is registered 分组。4.4 返回的 SpanIds 结构return { spanId, // OTEL span ID16 位十六进制 traceId, // OTEL trace ID32 位十六进制 ...(parentIsMastraSpan ? { parentSpanId } : { externalParentSpanId: parentSpanId }), };返回值区分两类父级parentIsMastraSpan为 true父 span 也是 Mastra span在otelSpanMap中或来自持久化恢复返回parentSpanId说明父子都在 Mastra trace 内否则父 span 属于外部 OTEL 系统如自动埋点产生的 ambient span返回externalParentSpanId表明这是一个桥接的根 span。测试 bridge.test.ts 验证了四种父子分类恢复的 Mastra 父级不会被误判为外部、ambient 父级会被正确报告为 external、以及通过executeInContext创建的嵌套 Mastra span 其父级仍被识别为内部。4.5 Span 映射表与生命周期桥内部维护otelSpanMap: Mapstring, { otelSpan, otelContext }bridge.ts以 Mastra span ID 为键记录对应 OTEL span 及其激活上下文。span 结束时handleSpanEnded会先从 map 中删除条目防止内存泄漏再用SpanConverter将 Mastra span 转换为符合 GenAI 语义约定的 OTEL ReadableSpan回写属性、状态、异常事件后以真实 end time 结束 OTEL spanbridge.ts若 span 被导出过滤excludeSpanTypes、spanFilter、span output processor丢弃则调用releaseSpan仅删除 map 条目而不结束底层 OTEL span避免导出用户已过滤掉的数据bridge.ts。测试 bridge.test.ts 的 span map cleanup 分组验证了四种情况下 map 都能清空为 0正常导出、被excludeSpanTypes丢弃、被spanFilter丢弃、被 output processor 丢弃。五、核心机制二executeInContext —— 让自动埋点代码挂到正确父级这是桥实现双向集成的另一个关键能力对应接口定义在 packages/core/src/observability/types/core.ts。executeInContext/executeInContextSync都委托给executeWithSpanContextbridge.tsprivate executeWithSpanContextT(spanId: string, fn: () T): T { const entry this.otelSpanMap.get(spanId); const spanContext entry?.otelContext; if (spanContext) { return otelContext.with(spanContext, fn); // 在 OTEL context 中执行 fn } return fn(); // span 不存在时直接执行 }原理otelContext.with(spanContext, fn)将 OTEL 的 contextAsyncLocalStorage设为指定的 span context再执行fn。这样 fn 内部任何被 OTEL 自动埋点的操作HTTP 客户端、数据库驱动都会以该 Mastra span 为父级创建 span保证 trace 树完整。executeInContext支持异步函数返回PromiseTexecuteInContextSync支持同步函数当 span 不存在时两者都退化为直接执行fn()不会报错对应测试 bridge.test.ts 与 L456-L469。六、日志桥接onLogEvent 与 trace 关联OtelBridge同时实现了日志桥接将 Mastra 的日志事件转发到全局或自定义OTEL LoggerProviderbridge.tsasync onLogEvent(event: LogEvent): Promisevoid { if (this.isDisabled) return; const params convertLog(event.log); // 复用 mastra/otel-exporter 的日志转换器 const attributes { ...params.attributes }; if (params.traceId) attributes[mastra.traceId] params.traceId; if (params.spanId) attributes[mastra.spanId] params.spanId; const logContext this.resolveLogContext(params.traceId, params.spanId); this.otelLogger.emit({ timestamp, severityNumber, severityText, body, attributes, context: logContext }); }日志的 trace 关联遵循三级回退resolveLogContextbridge.ts优先使用 map 中存储的 OTEL context若日志携带的 spanId 对应一个活跃的 Mastra span则在该 span 的 context 下发射日志日志会嵌套在 trace 中对应 span 之下用原始 ID 构造 SpanContextspan 不在本地 map如跨进程、孤儿子日志时若 traceIdspanId 能通过isSpanContextValid校验则构造TraceFlags.SAMPLED的 span context 让后端仍能按 ID 关联回退到当前活跃 context。同时日志会附带mastra.traceId与mastra.spanId属性JSON 兼容见测试 bridge.test.ts。日志级别会映射为 OTEL 的SeverityNumber/SeverityText例如warn→SeverityNumber.WARN/WARN见测试 L595-L606。优雅降级如果用户没有注册 LoggerProviderapi-logs会返回 no-op loggeremit()是静默空操作桥不会抛错bridge.ts 与测试 bridge.test.ts。七、span 属性与 GenAI 语义约定span 结束时桥通过SpanConverter来自mastra/otel-exporter统一格式化 spanbridge.ts初始化时指定format: GenAI_v1_38_0即采用 GenAI 语义约定格式bridge.tsspan 名称会更新为 converter 格式化后的名称otelSpan.updateName(readableSpan.name)所有标量属性非对象、非 null/undefined写入 OTEL span 属性包括 OTEL 语义约定属性状态、异常事件recordException也会回写异常消息取自exception.message属性。例如根 span 的标签tags会被序列化为mastra.tags属性JSON 字符串而子 span 不会携带该属性空数组也不会出现测试 bridge.test.ts。这些转换逻辑的具体实现在 observability/otel-exporter/src/span-converter.ts 与 observability/otel-exporter/src/gen-ai-semantics.ts如果你想了解某个属性从 Mastra span 到 OTEL span 的具体映射规则可以继续深入这两个文件。八、flush 与 shutdown生命周期管理8.1 flush()flush()用于在不关闭桥的前提下冲刷缓冲的 span/日志bridge.ts非常适合 Serverless 场景在运行时实例被终止前确保所有 span 已导出同时保持桥可用于后续请求。async flush(): Promisevoid { await this.flushProvider(this.tracerProvider, tracer); await this.flushProvider(this.loggerProvider, logger); }flushProvider会检查 provider 是否实现了forceFlush方法实现了才调用否则仅记录 debug 日志不会抛错。8.2 shutdown()shutdown()bridge.ts按顺序执行先flush()冲刷所有待导出数据遍历otelSpanMap强制结束所有未正常关闭的 span记录 warn 日志清空 span map。九、错误处理与健壮性设计桥的错误处理贯穿所有关键路径可总结为以下几点场景行为依据未注册 OTEL SDKspan context 无效createSpan结束 span 并返回undefined核心回退到自研 ID 生成bridge.ts测试 L300-L387createSpan内部异常catch 后记录错误日志并返回undefinedbridge.ts测试 L208-L217传null触发span 结束事件找不到对应 OTEL spanwarn 日志后返回不阻塞bridge.ts日志发射异常catch 后记录[OtelBridge] Failed to emit log错误bridge.ts无 LoggerProviderno-op logger静默空操作bridge.tsflush时 provider 不支持forceFlushdebug 日志不抛错bridge.ts畸形持久化 trace ID忽略并回退到当前活跃上下文 / 新 trace测试 bridge.test.ts十、实战验证与深入路径单元测试observability/otel-bridge/src/bridge.test.ts 覆盖了 span 创建、ID 格式16 位 spanId / 32 位 traceId、父子分类、持久化恢复、无 SDK 回退、日志关联、自定义 provider、span map 清理等全部核心行为。运行方式# 在仓库 observability/otel-bridge 目录下 pnpm test # vitest run pnpm test:watch # 监听模式完整集成测试单元测试文件头部注释指出带真实 OTEL 基础设施的集成测试位于observability/_examples/agent-hub/src/integration.test.ts想要验证桥与真实 SDK 协同工作的读者可以前往该目录查看。进一步阅读源码桥的实现observability/otel-bridge/src/bridge.ts桥的接口契约packages/core/src/observability/types/core.tsspan 转换器GenAI 语义格式化observability/otel-exporter/src/span-converter.ts日志转换器observability/otel-exporter/src/log-converter.ts父级解析工具getExternalParentIdobservability/mastra/src/spans/base.ts版本历史observability/otel-bridge/CHANGELOG.md。小结mastra/otel-bridge是 Mastra 与 OpenTelemetry 生态之间的关键桥梁createSpan保证 Mastra 每次 span 创建都能映射为真实 OTEL span 并正确继承父上下文executeInContext让自动埋点的外部调用嵌套到正确父级onLogEvent让日志与 trace 关联flush/shutdown保证数据可靠导出。理解 span map 的生命周期与无效 context 的兜底策略是正确使用桥的前提——只要记住使用前先注册 OTEL SDK必要时显式传入自定义 provider就能把 Mastra Agent 的完整执行链路平滑接入你现有的 OTEL 可观测平台。【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表