Mastra OpenTelemetry 导出器:将 Agent 可观测性数据接入任意 OTLP 平台
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
@mastra/otel-exporter是 Mastra 框架的官方 OpenTelemetry 导出组件,负责把 Mastra 运行时产生的 Trace(追踪)与 Log(日志)信号转换为符合 OpenTelemetry 语义约定的数据,并发送到任何兼容 OTLP 的可观测性平台。本文将以 observability/otel-exporter/README.md 为主线,结合包内源码与测试,完整讲解安装、配置、内置服务商支持、数据转换原理与排错方法,帮助你把 Agent、工作流与工具调用的全链路观测数据统一接入 Dash0、SigNoz、New Relic、Traceloop、Laminar 或自建 Collector。
包定位与能力概览
@mastra/otel-exporter属于 observability 目录下的独立发布包(包名@mastra/otel-exporter,见 package.json)。它不是一个完整的可观测性后端,而是一个"导出器"(Exporter):上游是 Mastra 核心(@mastra/core/observability)产生的标准 span 与 log 事件,下游是任意支持 OTLP 的采集端。
从源码结构看,该包的核心职责有三块:
- 信号导出:同时支持 Trace 与 Log 两种信号,可分别开关(tracing.ts)。
- 语义转换:把 Mastra 自有的 span 类型(模型生成、工具调用、Agent 运行、工作流控制流等)映射为 OpenTelemetry GenAI 语义约定下的 span、属性与日志(span-converter.ts、gen-ai-semantics.ts、log-converter.ts)。
- 服务商适配:为 Dash0、SigNoz、New Relic、Traceloop、Laminar 以及任意自定义 OTLP 端点解析出正确的 endpoint、认证头与传输协议(provider-configs.ts)。
README 中特别标注了一条重要注意事项:该包要求根据所选服务商额外安装对应的导出器依赖包。这些依赖在 package.json 中被声明为可选 peerDependencies 与 optionalDependencies,运行期由 loadExporter.ts 按需动态加载——装少了会打印明确的安装提示,但不会导致整个进程崩溃。
安装与前置条件
基础包安装:
npm install @mastra/otel-exporter运行时环境要求 Node.js >= 22.13.0(见 package.json 的 engines 字段)。包本身是 ESM 优先的双格式产物(dist/index.js与dist/index.cjs),同时支持import与require。
由于导出器采用"信号 + 协议"的二维矩阵动态加载模型(loadExporter.ts),不同传输协议对应不同 npm 包:
| 协议 | Trace 导出包 | Log 导出包 | 附加包 |
|---|---|---|---|
http/json | @opentelemetry/exporter-trace-otlp-http | @opentelemetry/exporter-logs-otlp-http | — |
http/protobuf | @opentelemetry/exporter-trace-otlp-proto | @opentelemetry/exporter-logs-otlp-proto | — |
grpc | @opentelemetry/exporter-trace-otlp-grpc | @opentelemetry/exporter-logs-otlp-grpc | @grpc/grpc-js |
zipkin | @opentelemetry/exporter-zipkin | 不支持(Log 信号下会自动禁用并给出警告) | — |
例如 Dash0 走 gRPC 协议,就需要:
npm install @opentelemetry/exporter-trace-otlp-grpc @grpc/grpc-js如果希望同时导出 Log 信号,还要安装对应的@opentelemetry/exporter-logs-otlp-grpc。不需要提前安装所有组合:加载器会在初始化时按实际协议动态import对应包,缺失时打印形如Install the required package(s): npm install @opentelemetry/exporter-trace-otlp-proto的提示并禁用该信号(loadExporter.ts)。
基本接入:在 Mastra 中挂载导出器
README 给出的最小接入方式是:创建一个Mastra实例,通过@mastra/observability的Observability配置注册OtelExporter,并设置标准的OTEL_EXPORTER_OTLP_*环境变量指向你的 Collector。
import { Mastra } from '@mastra/core/mastra'; import { Observability } from '@mastra/observability'; import { OtelExporter } from '@mastra/otel-exporter'; export const mastra = new Mastra({ observability: new Observability({ configs: { otel: { serviceName: 'my-service', exporters: [new OtelExporter({ provider: { dash0: {} } })], }, }, }), });其中:
serviceName会被写入导出数据的service.name资源属性,用于在后端平台区分服务;缺省时回退为mastra-service(tracing.ts)。exporters数组可以注册多个导出器。OtelExporter内部的name固定为opentelemetry(tracing.ts)。provider字段支持的对象形状见下文。
README 提到"设置标准OTEL_EXPORTER_OTLP_*环境变量",但需要说明适用边界:该包并未直接读取OTEL_EXPORTER_OTLP_ENDPOINT等环境变量来构造 endpoint(endpoint 由各 provider 配置或服务商专用环境变量决定,见下节),OTEL_EXPORTER_OTLP_*主要用于自建 Collector 配合customprovider 时的习惯性配置以及 OTel SDK 内部行为。最可靠的路径是:用customprovider 显式指定 endpoint,或直接使用内置服务商的专用配置。
Provider 配置详解:六种接入方式与完整参数表
Provider 类型定义在 types.ts,共六种:dash0、signoz、newrelic、traceloop、laminar、custom。解析逻辑集中在 provider-configs.ts,每种配置都支持"代码传入优先、环境变量兜底"的取值策略,并且所有字段均可选——必填项会在运行期校验,缺失时打印错误并禁用 trace 导出(返回 null),而不是抛异常。
Dash0(gRPC)
new OtelExporter({ provider: { dash0: { apiKey, endpoint, dataset } } })| 字段 | 环境变量 | 说明 |
|---|---|---|
apiKey | DASH0_API_KEY | 必填(运行期校验),以authorization: Bearer <key>形式发送 |
endpoint | DASH0_ENDPOINT | 必填,形如ingress.us-west-2.aws.dash0.com:4317 |
dataset | DASH0_DATASET | 可选,通过dash0-dataset头指定数据集 |
Dash0 默认使用 gRPC 协议,endpoint 缺少/v1/traces后缀时会自动补齐(provider-configs.ts)。测试用例验证了最终 endpoint 为ingress.us-west-2.aws.dash0.com:4317/v1/traces、header 为authorization: Bearer test-key(provider-configs.test.ts)。
SigNoz(HTTP/protobuf)
new OtelExporter({ provider: { signoz: { apiKey, region, endpoint } } })| 字段 | 环境变量 | 说明 |
|---|---|---|
apiKey | SIGNOZ_API_KEY | 必填,通过signoz-ingestion-key头发送 |
region | SIGNOZ_REGION | us/eu/in,云版端点https://ingest.<region>.signoz.cloud:443/v1/traces |
endpoint | SIGNOZ_ENDPOINT | 自托管 SigNoz 时填写,覆盖 region 推导的云端点 |
New Relic(HTTP/protobuf)
new OtelExporter({ provider: { newrelic: { apiKey, endpoint } } })| 字段 | 环境变量 | 说明 |
|---|---|---|
apiKey | NEW_RELIC_LICENSE_KEY | 必填(License Key),通过api-key头发送 |
endpoint | NEW_RELIC_ENDPOINT | 可选,EU 区或自定义端点;默认https://otlp.nr-data.net:443/v1/traces |
Traceloop(HTTP/json)
new OtelExporter({ provider: { traceloop: { apiKey, destinationId, endpoint } } })| 字段 | 环境变量 | 说明 |
|---|---|---|
apiKey | TRACELOOP_API_KEY | 必填,Authorization: Bearer <key> |
destinationId | TRACELOOP_DESTINATION_ID | 可选,通过x-traceloop-destination-id头指定目标 |
endpoint | TRACELOOP_ENDPOINT | 默认https://api.traceloop.com/v1/traces |
Laminar(HTTP/protobuf)
new OtelExporter({ provider: { laminar: { apiKey, endpoint } } })| 字段 | 环境变量 | 说明 |
|---|---|---|
apiKey | LMNR_PROJECT_API_KEY | 必填,使用 Laminar 标准环境变量名 |
endpoint | LAMINAR_ENDPOINT | 默认https://api.lmnr.ai/v1/traces |
Custom(任意 OTLP 端点 / Collector)
new OtelExporter({ provider: { custom: { endpoint: 'http://localhost:4318', // 必填 headers: { Authorization: 'Bearer ...' }, protocol: 'http/protobuf', // 默认 http/json }, }, })custom是接入自建 OpenTelemetry Collector、或任何未内置服务商的最通用方式:endpoint必填,headers与protocol(http/json/http/protobuf/grpc/zipkin)可选。注意custom模式下不会自动补/v1/traces后缀——endpoint需要你直接给出完整路径。
各 Provider 解析行为一览
以下汇总自 provider-configs.ts 的实现:
| Provider | 默认协议 | 认证头 | 默认 Endpoint 规则 |
|---|---|---|---|
| dash0 | grpc | authorization: Bearer <key>、dash0-dataset | 自动追加/v1/traces |
| signoz | http/protobuf | signoz-ingestion-key | https://ingest.<region>.signoz.cloud:443/v1/traces |
| newrelic | http/protobuf | api-key | https://otlp.nr-data.net:443/v1/traces |
| traceloop | http/json | Authorization: Bearer <key>、x-traceloop-destination-id | https://api.traceloop.com/v1/traces |
| laminar | http/protobuf | Authorization: Bearer <key> | https://api.lmnr.ai/v1/traces |
| custom | http/json(可配) | 自定义 headers | 直接使用传入 endpoint |
解析器只从配置对象读取第一个 key 来确定 provider 类型(provider-configs.ts),因此一个provider对象内只能出现一个服务商键。另外,debug 模式下打印解析结果时,header 值会被统一替换为[REDACTED],避免 API Key 等凭据泄漏到日志中(provider-configs.ts)。
OtelExporterConfig 完整配置项
OtelExporter的构造参数在 types.ts 中定义,除provider外还包括:
| 配置项 | 类型 | 默认值 | 作用 |
|---|---|---|---|
provider | ProviderConfig | 必填 | 服务商配置;不提供时导出器直接置为禁用态 |
timeout | number(毫秒) | 30000 | 单批导出超时,同时作为 exportTimeoutMillis |
batchSize | number | 512 | 单批最大导出条数(maxExportBatchSize) |
logLevel | 'debug' \| 'info' \| 'warn' \| 'error' | — | 设为debug时启用导出结果日志与诊断 |
resourceAttributes | DetectedResourceAttributes | — | 附加/覆盖导出数据的资源属性 |
exporter | SpanExporter | — | 直接注入自定义 span exporter,跳过内置构建 |
signals.traces | boolean | true | 关闭 trace 导出 |
signals.logs | boolean | true | 关闭 log 导出 |
源码 tracing.ts 展示了 batch 处理的完整默认参数:maxExportBatchSize = batchSize || 512、maxQueueSize = 2048、scheduledDelayMillis = 5000(每 5 秒批量发送一次)、exportTimeoutMillis = timeout || 30000。也就是说,span 并不是实时逐条发送的,而是进入 2048 容量的队列后每 5 秒批量冲刷,理解这一点对排查"数据延迟到达"类问题很有帮助。
logLevel: 'debug'时,导出器会做两件事:一是将 OTel SDK 的 diag 日志级别设为 INFO(刻意不用 DEBUG,因为 OTLP 导出器在 DEBUG 级别会输出巨大 payload,且diag.setLogger是全局的、可能被其他代码覆盖);二是用DebugSpanExporterWrapper/DebugLogExporterWrapper包装真实 exporter,在每次批量导出成功或失败时输出日志——OTel SDK 默认在成功时不打印任何日志,这个包装是排查导出问题的关键手段(tracing.ts)。
Span 转换:Mastra 类型到 OTel GenAI 语义
Mastra 的 span 通过 span-converter.ts 转换为 OTelReadableSpan,转换格式当前固定为GenAI_v1_38_0,即遵循 OpenTelemetry GenAI 语义约定 v1.38.0。
Span 名称与 Kind
- Span 名称由
getSpanName生成(gen-ai-semantics.ts):优先组合"操作名 + 实体标识",例如模型生成产生chat gpt-4o这样的名字;工作流控制流类 span 因没有自己的实体名,回退到净化后的原始 span 名。操作名映射关系(gen_ai.operation.name)为:MODEL_GENERATION → chat、RAG_EMBEDDING → embeddings、各类工具调用 →execute_tool、AGENT_RUN → invoke_agent、WORKFLOW_RUN → invoke_workflow(gen-ai-semantics.ts)。 - Span Kind:
MODEL_GENERATION、RAG_EMBEDDING、MCP_TOOL_CALL映射为CLIENT,其余为INTERNAL(span-converter.ts)。
资源与 Instrumentation Scope
span 上会附带标准资源属性:service.name(来自 observability 配置或mastra-service回退)、service.version(取自@mastra/core包版本)、telemetry.sdk.name、telemetry.sdk.version、telemetry.sdk.language: nodejs;resourceAttributes中重复的键会覆盖上述默认值(span-converter.ts)。
丰富的 GenAI 属性
gen-ai-semantics.ts 是语义转换的核心,按 span 类型注入:
- 模型生成(MODEL_GENERATION):请求/响应模型名、provider 名(经
normalizeProvider归一化,内置了 anthropic、openai、gemini、bedrock、vertex_ai 等别名映射表,见 gen-ai-semantics.ts)、temperature/maxOutputTokens/topP/topK/presencePenalty/frequencyPenalty/stopSequences/seed等请求参数、输入/输出消息(转为 GenAI 语义消息)、finishReason、responseId、服务地址端口;还会额外输出mastra.completion_start_time用于后端计算首 token 延迟(TTFT)。 - Token 用量:
formatUsageMetrics输出gen_ai.usage.input_tokens、gen_ai.usage.output_tokens、缓存读写 token(cache_read/cache_creation及 5m/1h 变体)、reasoning_tokens、音频 token 等(gen-ai-semantics.ts)。 - 工具调用:
gen_ai.tool.name、gen_ai.tool.call.id、gen_ai.tool.call.arguments、gen_ai.tool.call.result、gen_ai.tool.type、工具描述;MCP 调用还会附带 MCP server 地址与版本。 - Agent:
gen_ai.agent.id、gen_ai.agent.name、gen_ai.conversation_id(线程 ID 也会映射到该字段)、gen_ai.system_instructions、可用工具列表。 - 工作流:运行状态、步骤 id、条件分支数/选中步骤、并行分支数、循环类型与迭代次数、sleep 时长、wait 事件名等,统一以
mastra.<span_type>.<snake_case>命名空间输出(gen-ai-semantics.ts)。 - 通用:
mastra.span.type、输入输出(模型与工具走gen_ai.*约定,其他类型落到mastra.<type>.input/output)、mastra.metadata.*、根 span 的mastra.tags(JSON 序列化以兼容 Jaeger/Zipkin/Tempo 等对数组支持不佳的后端,见 span-converter.ts)。
状态与异常
span 存在errorInfo时,状态置为ERROR并写入exception事件(含exception.message、exception.type、exception.stacktrace),同时error.type、error.message、error.domain、error.category被写入属性;正常结束的 span 置为OK,未结束的置为UNSET(span-converter.ts)。
父级关系上,Mastra 的externalParentSpanId会被保留为 OTel 侧父 span,使外部注入的上下文能够延续(span-converter.ts)。
Log 转换:日志信号与 Trace 关联
Log 信号由 log-converter.ts 处理:
- 严重级别映射:Mastra 的
debug/info/warn/error/fatal对应 OTelSeverityNumber的DEBUG/INFO/WARN/ERROR/FATAL,severityText为对应大写字符串。 - 属性构建:结构化数据
log.data与log.metadata分别映射为mastra.log.<key>、mastra.metadata.<key>;对象值做 JSON 序列化,遇到循环引用等无法序列化的值回退为[unserializable]占位符;标签写入mastra.tags。 - Trace 关联:日志在发出时会把
traceId/spanId既写入mastra.traceId/mastra.spanId属性,又通过trace.setSpanContext镜像到 OTel 日志 Context 的标准字段(tracing.ts),从而让 Grafana、Datadog、Honeycomb 等后端能用标准 OTLP 字段把日志与 trace 关联起来。日志导出器同样使用 512/2048/5s/30s 的 batch 参数(tracing.ts)。
生命周期:flush 与 shutdown
OtelExporter实现了完整生命周期(tracing.ts):
- init:由 Mastra 在组件注册时调用,触发所有信号的并行异步初始化,事件处理器会等待初始化 promise 完成后再处理数据;即使没有经过 Mastra,首次收到事件时也会惰性触发同样的初始化。
- flush:先等待初始化完成,再对所有已激活的 processor/provider 执行
forceFlush,适合在服务优雅退出前冲刷缓冲数据。 - shutdown:等待初始化后关闭所有 processor 与 provider,释放连接。
故障排查速查表
| 现象 | 可能原因 | 排查手段 |
|---|---|---|
| Trace 未发送 | 缺少对应协议的导出器包 | 查看启动日志中Install the required package(s):提示,按协议安装 |
输出Provider configuration is required | 未传provider | 使用customprovider 指定通用端点 |
输出...requires apiKey... Tracing will be disabled | 未配置服务商 API Key | 设置对应环境变量或在配置中传入 |
| 数据延迟 5 秒以上到达 | batch 机制(scheduledDelayMillis=5000) | 属正常行为;如需实时可调小或接受批量语义 |
| 需要确认导出成功/失败 | OTel SDK 成功时不打日志 | 设logLevel: 'debug',观察Export completed/Export FAILED日志 |
| 日志信号缺失 | 未安装 logs 系列导出包,或signals.logs: false | 安装@opentelemetry/exporter-logs-otlp-*并确认开关 |
| 服务名不对 | serviceName未传 | 在Observability的otel配置中设置serviceName |
小结
@mastra/otel-exporter把"Mastra 应用 → OTLP 平台"这条链路封装成了开箱即用的配置式接入:通过provider一个字段即可完成 endpoint、协议、认证头的全部推导;通过SpanConverter与 GenAI 语义转换,Mastra 的 Agent、工具、工作流原生 span 被翻译成各可观测性后端都能理解的标准模型;batch 机制、debug 日志、信号开关与生命周期管理则保证了生产环境下的可控性。对于尚未内置的服务商,customprovider 可以对接任意 OTLP Collector,因此本文覆盖的接入模式也适用于自建监控基础设施的场景。
延伸阅读
- 包内自述与变更记录:README.md、CHANGELOG.md
- 类型定义与配置接口:types.ts
- Provider 解析与测试:provider-configs.ts、provider-configs.test.ts
- 信号导出与生命周期:tracing.ts
- 语义转换实现与测试:span-converter.ts、gen-ai-semantics.ts、log-converter.ts
- 动态依赖加载:loadExporter.ts
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考