@mastra/otel-bridge 1.5.16-alpha.1 → 1.5.16-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bridge.d.ts CHANGED
@@ -54,6 +54,7 @@ export declare class OtelBridge extends BaseExporter implements ObservabilityBri
54
54
  private otelSpanMap;
55
55
  private spanConverter?;
56
56
  private hasCustomTracerProvider;
57
+ private hasCustomLoggerProvider;
57
58
  private warnedNoTracerProvider;
58
59
  constructor(config?: OtelBridgeConfig);
59
60
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,mBAAmB,EACnB,YAAY,EACZ,QAAQ,EACR,iBAAiB,EACjB,QAAQ,EACR,OAAO,EACP,mBAAmB,EAEpB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,YAAY,EAAuB,MAAM,uBAAuB,CAAC;AAC1E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAGhE,OAAO,KAAK,EAAkD,cAAc,EAAU,MAAM,oBAAoB,CAAC;AAEjH,OAAO,KAAK,EAAwB,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAgBpF,MAAM,MAAM,gBAAgB,GAAG,kBAAkB,GAAG;IAClD,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,UAAW,SAAQ,YAAa,YAAW,mBAAmB;IACzE,IAAI,SAAU;IACd,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAa;IAC/B,qFAAqF;IACrF,OAAO,CAAC,WAAW,CAAiG;IACpH,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC,OAAO,CAAC,uBAAuB,CAAU;IACzC,OAAO,CAAC,sBAAsB,CAAS;IAEvC,YAAY,MAAM,GAAE,gBAAqB,EAOxC;IAED;;;;;;;OAOG;IACH,UAAgB,mBAAmB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAItE;IAED;;;;;;;;;;;;;OAaG;IACG,UAAU,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB/C;IAED;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA0BzB;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE,mBAAmB,QAMhC;IAED;;;;;;OAMG;IACH,UAAU,CAAC,OAAO,EAAE,iBAAiB,CAAC,QAAQ,CAAC,GAAG,OAAO,GAAG,SAAS,CA8GpE;IAED;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAiB5B;;;;;OAKG;YACW,eAAe;IAiE7B;;;;;;OAMG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAMhC;IAED;;;;;;;;;OASG;IACH,OAAO,CAAC,sBAAsB;IAgB9B;;;;;;OAMG;IACH,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAEpE;IAED;;;;;;OAMG;IACH,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAEtD;IAED;;;;;;;OAOG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAG3B;YAEa,aAAa;IAoB3B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAW9B;CACF"}
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,mBAAmB,EACnB,YAAY,EACZ,QAAQ,EACR,iBAAiB,EACjB,QAAQ,EACR,OAAO,EACP,mBAAmB,EAEpB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,YAAY,EAAuB,MAAM,uBAAuB,CAAC;AAC1E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAGhE,OAAO,KAAK,EAAkD,cAAc,EAAU,MAAM,oBAAoB,CAAC;AAEjH,OAAO,KAAK,EAAwB,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAgBpF,MAAM,MAAM,gBAAgB,GAAG,kBAAkB,GAAG;IAClD,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,UAAW,SAAQ,YAAa,YAAW,mBAAmB;IACzE,IAAI,SAAU;IACd,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAa;IAC/B,qFAAqF;IACrF,OAAO,CAAC,WAAW,CAAiG;IACpH,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC,OAAO,CAAC,uBAAuB,CAAU;IACzC,OAAO,CAAC,uBAAuB,CAAU;IACzC,OAAO,CAAC,sBAAsB,CAAS;IAEvC,YAAY,MAAM,GAAE,gBAAqB,EAQxC;IAED;;;;;;;OAOG;IACH,UAAgB,mBAAmB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAItE;IAED;;;;;;;;;;;;;OAaG;IACG,UAAU,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB/C;IAED;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA0BzB;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE,mBAAmB,QAMhC;IAED;;;;;;OAMG;IACH,UAAU,CAAC,OAAO,EAAE,iBAAiB,CAAC,QAAQ,CAAC,GAAG,OAAO,GAAG,SAAS,CA8GpE;IAED;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAiB5B;;;;;OAKG;YACW,eAAe;IAiE7B;;;;;;OAMG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAMhC;IAED;;;;;;;;;OASG;IACH,OAAO,CAAC,sBAAsB;IAgB9B;;;;;;OAMG;IACH,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAEpE;IAED;;;;;;OAMG;IACH,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAEtD;IAED;;;;;;;OAOG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAQ3B;YAEa,aAAa;IA+B3B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAU9B;CACF"}
package/dist/index.cjs CHANGED
@@ -52,10 +52,12 @@ var OtelBridge = class extends _mastra_observability.BaseExporter {
52
52
  otelSpanMap = /* @__PURE__ */ new Map();
53
53
  spanConverter;
54
54
  hasCustomTracerProvider;
55
+ hasCustomLoggerProvider;
55
56
  warnedNoTracerProvider = false;
56
57
  constructor(config = {}) {
57
58
  super(config);
58
59
  this.hasCustomTracerProvider = config.tracerProvider !== void 0;
60
+ this.hasCustomLoggerProvider = config.loggerProvider !== void 0;
59
61
  this.tracerProvider = config.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
60
62
  this.otelTracer = this.tracerProvider.getTracer("@mastra/otel-bridge", "1.0.0");
61
63
  this.loggerProvider = config.loggerProvider ?? _opentelemetry_api_logs.logs.getLoggerProvider();
@@ -297,11 +299,14 @@ var OtelBridge = class extends _mastra_observability.BaseExporter {
297
299
  * instance is terminated.
298
300
  */
299
301
  async flush() {
300
- await this.flushProvider(this.tracerProvider, "tracer");
301
- await this.flushProvider(this.loggerProvider, "logger");
302
+ const tracerProvider = this.hasCustomTracerProvider ? this.tracerProvider : _opentelemetry_api.trace.getTracerProvider();
303
+ const loggerProvider = this.hasCustomLoggerProvider ? this.loggerProvider : _opentelemetry_api_logs.logs.getLoggerProvider();
304
+ await this.flushProvider(tracerProvider, "tracer");
305
+ await this.flushProvider(loggerProvider, "logger");
302
306
  }
303
307
  async flushProvider(provider, label) {
304
308
  try {
309
+ if (provider && typeof provider === "object" && "getDelegate" in provider && typeof provider.getDelegate === "function") provider = provider.getDelegate();
305
310
  if (provider && typeof provider === "object" && "forceFlush" in provider && typeof provider.forceFlush === "function") {
306
311
  await provider.forceFlush();
307
312
  this.logger.debug(`[OtelBridge] Flushed ${label} provider`);
@@ -314,12 +319,12 @@ var OtelBridge = class extends _mastra_observability.BaseExporter {
314
319
  * Shutdown the bridge and clean up resources
315
320
  */
316
321
  async shutdown() {
317
- await this.flush();
318
322
  for (const [spanId, { otelSpan }] of this.otelSpanMap.entries()) {
319
323
  this.logger.warn(`[OtelBridge] Force-ending span that was not properly closed [id=${spanId}]`);
320
324
  otelSpan.end();
321
325
  }
322
326
  this.otelSpanMap.clear();
327
+ await this.flush();
323
328
  this.logger.info("[OtelBridge] Shutdown complete");
324
329
  }
325
330
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["TraceFlags","BaseExporter","otelTrace","otelLogs","TracingEventType","otelContext","SpanConverter"],"sources":["../src/bridge.ts"],"sourcesContent":["/**\n * OpenTelemetry Bridge for Mastra Observability\n *\n * This bridge enables bidirectional integration with OpenTelemetry infrastructure:\n * 1. Reads OTEL trace context from active spans (via AsyncLocalStorage)\n * 2. Creates real OTEL spans when Mastra spans are created\n * 3. Maintains span context for proper parent-child relationships\n * 4. Allows OTEL-instrumented code (DB, HTTP clients) in tools/workflows to have correct parents\n *\n * This creates complete distributed traces where Mastra spans are properly\n * nested within OTEL spans from auto-instrumentation, and any OTEL-instrumented\n * operations within Mastra spans maintain the correct hierarchy.\n */\n\nimport type {\n ObservabilityBridge,\n TracingEvent,\n LogEvent,\n CreateSpanOptions,\n SpanType,\n SpanIds,\n InitExporterOptions,\n SpanLink,\n} from '@mastra/core/observability';\nimport { TracingEventType } from '@mastra/core/observability';\nimport { BaseExporter, getExternalParentId } from '@mastra/observability';\nimport type { BaseExporterConfig } from '@mastra/observability';\nimport { SpanConverter, convertLog, getSpanKind } from '@mastra/otel-exporter';\nimport { trace as otelTrace, context as otelContext, isSpanContextValid, TraceFlags } from '@opentelemetry/api';\nimport type { Span as OtelSpan, Context as OtelContext, Link, TracerProvider, Tracer } from '@opentelemetry/api';\nimport { logs as otelLogs } from '@opentelemetry/api-logs';\nimport type { Logger as OtelLogger, LoggerProvider } from '@opentelemetry/api-logs';\n\nfunction linkKey(link: { traceId: string; spanId: string }): string {\n return `${link.traceId}:${link.spanId}`;\n}\n\n/** OpenTelemetry links for Mastra span links, skipping any whose IDs are not valid OpenTelemetry IDs. */\nfunction toOtelLinks(links: SpanLink[]): Link[] {\n return links\n .map(link => ({ traceId: link.traceId, spanId: link.spanId, traceFlags: TraceFlags.SAMPLED, isRemote: true }))\n .filter(context => isSpanContextValid(context))\n .map(context => ({ context }));\n}\n\nconst SETUP_DOCS_URL = 'https://mastra.ai/reference/observability/tracing/bridges/otel#setup-requirements';\n\nexport type OtelBridgeConfig = BaseExporterConfig & {\n tracerProvider?: TracerProvider;\n loggerProvider?: LoggerProvider;\n};\n\n/**\n * OpenTelemetry Bridge implementation\n *\n * Creates real OTEL spans when Mastra spans are created, maintaining proper\n * context propagation for nested instrumentation.\n *\n * @example\n * ```typescript\n * import { OtelBridge } from '@mastra/otel-bridge';\n * import { Mastra } from '@mastra/core';\n *\n * const mastra = new Mastra({\n * agents: { myAgent },\n * observability: {\n * configs: {\n * default: {\n * serviceName: 'my-service',\n * bridge: new OtelBridge(),\n * }\n * }\n * }\n * });\n * ```\n */\nexport class OtelBridge extends BaseExporter implements ObservabilityBridge {\n name = 'otel';\n private tracerProvider: TracerProvider;\n private loggerProvider: LoggerProvider;\n private otelTracer: Tracer;\n private otelLogger: OtelLogger;\n /** Live OTEL spans by span ID, with the keys of the links they were started with. */\n private otelSpanMap = new Map<string, { otelSpan: OtelSpan; otelContext: OtelContext; startLinks?: Set<string> }>();\n private spanConverter?: SpanConverter;\n private hasCustomTracerProvider: boolean;\n private warnedNoTracerProvider = false;\n\n constructor(config: OtelBridgeConfig = {}) {\n super(config);\n this.hasCustomTracerProvider = config.tracerProvider !== undefined;\n this.tracerProvider = config.tracerProvider ?? otelTrace.getTracerProvider();\n this.otelTracer = this.tracerProvider.getTracer('@mastra/otel-bridge', '1.0.0');\n this.loggerProvider = config.loggerProvider ?? otelLogs.getLoggerProvider();\n this.otelLogger = this.loggerProvider.getLogger('@mastra/otel-bridge', '1.0.0');\n }\n\n /**\n * Handle Mastra tracing events\n *\n * Ships OTEL spans when Mastra spans end.\n * This maintains proper span hierarchy and allows OTEL-instrumented code within\n * Mastra spans to have correct parent-child relationships.\n * Note: OTEL spans are created when registerSpan is called when the span is first created.\n */\n protected async _exportTracingEvent(event: TracingEvent): Promise<void> {\n if (event.type === TracingEventType.SPAN_ENDED) {\n await this.handleSpanEnded(event);\n }\n }\n\n /**\n * Forward Mastra log events into the globally-registered OTEL LoggerProvider.\n *\n * If the user has not registered a LoggerProvider (e.g. via @opentelemetry/sdk-logs\n * or NodeSDK's logRecordProcessor option), the API returns a no-op logger and\n * emit() is a silent no-op — the bridge degrades gracefully.\n *\n * Trace correlation:\n * - If the log carries a spanId we have an OTEL span for, emit under that span's\n * stored context so the log nests beneath it in the trace.\n * - Else if the log carries traceId+spanId, attach a SpanContext built from those\n * IDs so backends still correlate by ID.\n * - Else emit under whatever context is currently active.\n */\n async onLogEvent(event: LogEvent): Promise<void> {\n if (this.isDisabled) return;\n\n try {\n const params = convertLog(event.log);\n\n const attributes = { ...params.attributes };\n if (params.traceId) attributes['mastra.traceId'] = params.traceId;\n if (params.spanId) attributes['mastra.spanId'] = params.spanId;\n\n const logContext = this.resolveLogContext(params.traceId, params.spanId);\n\n this.otelLogger.emit({\n timestamp: params.timestamp,\n severityNumber: params.severityNumber,\n severityText: params.severityText,\n body: params.body,\n attributes,\n context: logContext,\n });\n } catch (error) {\n this.logger.error('[OtelBridge] Failed to emit log:', error);\n }\n }\n\n /**\n * Pick the OTEL Context to emit a log under so trace correlation is correct.\n */\n private resolveLogContext(traceId?: string, spanId?: string): OtelContext {\n // 1. Prefer the stored OTEL context for the originating Mastra span.\n if (spanId) {\n const entry = this.otelSpanMap.get(spanId);\n if (entry) return entry.otelContext;\n }\n\n // 2. Fall back to a context with a span context built from the raw IDs,\n // but only when both IDs form a valid W3C span context. Injecting\n // malformed IDs would surface as garbage trace links downstream.\n if (traceId && spanId) {\n const candidate = {\n traceId,\n spanId,\n traceFlags: TraceFlags.SAMPLED,\n isRemote: false,\n };\n if (isSpanContextValid(candidate)) {\n return otelTrace.setSpanContext(otelContext.active(), candidate);\n }\n }\n\n // 3. Fall through to whatever is currently active.\n return otelContext.active();\n }\n\n /**\n * Initialize with tracing configuration\n */\n init(options: InitExporterOptions) {\n this.spanConverter = new SpanConverter({\n packageName: '@mastra/otel-bridge',\n serviceName: options.config?.serviceName,\n format: 'GenAI_v1_38_0',\n });\n }\n\n /**\n * Create a span in the bridge's tracing system.\n * Called during Mastra span construction to get bridge-generated identifiers.\n *\n * @param options - Span creation options from Mastra\n * @returns Span identifiers (spanId, traceId, parentSpanId) from bridge, or undefined if creation fails\n */\n createSpan(options: CreateSpanOptions<SpanType>): SpanIds | undefined {\n try {\n // Determine parent context\n let parentOtelContext = otelContext.active();\n let usedPersistedParent = false;\n\n // Get external parent ID (walks up chain to find non-internal parent)\n const externalParentId = getExternalParentId(options);\n if (externalParentId) {\n // Look up external parent's OTEL span from map\n const parentEntry = this.otelSpanMap.get(externalParentId);\n if (parentEntry) {\n parentOtelContext = parentEntry.otelContext;\n }\n } else if (options.traceId && options.parentSpanId) {\n // Root spans restored from persisted state (e.g. workflow suspend/resume)\n // carry the original trace ID and parent span ID, but the parent OTEL span\n // is no longer alive — it ended when the run suspended, possibly in another\n // process. Parent under a remote span context built from the persisted IDs\n // so the span continues the original trace instead of joining whatever\n // context happens to be active. Skip malformed IDs; injecting them would\n // surface as garbage trace links downstream.\n const candidate = {\n traceId: options.traceId,\n spanId: options.parentSpanId,\n traceFlags: TraceFlags.SAMPLED,\n isRemote: true,\n };\n if (isSpanContextValid(candidate)) {\n parentOtelContext = otelTrace.setSpanContext(parentOtelContext, candidate);\n usedPersistedParent = true;\n }\n }\n\n // Create OTEL span with SpanKind (must be set at creation, immutable)\n const otelSpan = this.otelTracer.startSpan(\n options.name,\n {\n kind: getSpanKind(options.type),\n ...(options.startTime ? { startTime: options.startTime } : {}),\n ...(options.links?.length ? { links: toOtelLinks(options.links) } : {}),\n },\n parentOtelContext,\n );\n\n // Create context with this span active\n const spanContext = otelTrace.setSpan(parentOtelContext, otelSpan);\n\n // Get OTEL span identifiers\n const otelSpanContext = otelSpan.spanContext();\n\n // If no OTEL SDK is registered, the global tracer is a no-op. With no\n // parent it returns a span with an invalid (all-zero) span context; with\n // a parent in the context (e.g. an outer span from another, unregistered\n // provider) it returns a span wrapping the parent's span context. Either\n // way, returning those IDs would collide across every Mastra span and\n // break downstream exporters. Bail out so DefaultSpan falls through to\n // its own ID generator. A real tracer always mints a fresh span ID, even\n // for spans its sampler drops.\n const parentOtelSpanContext = otelTrace.getSpanContext(parentOtelContext);\n if (\n !isSpanContextValid(otelSpanContext) ||\n (parentOtelSpanContext !== undefined && otelSpanContext.spanId === parentOtelSpanContext.spanId)\n ) {\n this.warnNoTracerProvider();\n // End the span we just started so its lifecycle stays clean on\n // providers that do track non-recording spans.\n otelSpan.end();\n return undefined;\n }\n\n const spanId = otelSpanContext.spanId;\n const traceId = otelSpanContext.traceId;\n\n // Store for later retrieval (for executeWithSpanContext and event handling)\n this.otelSpanMap.set(spanId, {\n otelSpan,\n otelContext: spanContext,\n ...(options.links?.length ? { startLinks: new Set(options.links.map(linkKey)) } : {}),\n });\n\n // Get parentSpanId from parent context if available\n const parentSpan = otelTrace.getSpan(parentOtelContext);\n const parentSpanContext = parentSpan?.spanContext();\n const parentSpanId =\n parentSpanContext && isSpanContextValid(parentSpanContext) ? parentSpanContext.spanId : undefined;\n\n // Declare which kind of parent was used: a span this bridge created for\n // a Mastra span is part of the Mastra trace, while anything else in the\n // ambient OTEL context belongs to the external tracing system. A parent\n // restored from persisted state is a Mastra span in storage — it ended at\n // suspend (possibly in another process), so it is absent from the live map.\n const parentIsMastraSpan =\n parentSpanId !== undefined && (this.otelSpanMap.has(parentSpanId) || usedPersistedParent);\n\n this.logger.debug(\n `[OtelBridge.createSpan] Created span [spanId=${spanId}] [traceId=${traceId}] ` +\n `[parentSpanId=${parentSpanId}] [parentIsMastraSpan=${parentIsMastraSpan}] ` +\n `[type=${options.type}] [mapSize=${this.otelSpanMap.size}]`,\n );\n\n return {\n spanId,\n traceId,\n ...(parentIsMastraSpan ? { parentSpanId } : { externalParentSpanId: parentSpanId }),\n };\n } catch (error) {\n this.logger.error('[OtelBridge] Failed to create span:', error);\n return undefined;\n }\n }\n\n /**\n * Warn once per bridge that no tracer provider is producing spans. Checked\n * lazily on span creation rather than at startup, because the OTEL SDK is\n * often registered after the bridge is constructed.\n */\n private warnNoTracerProvider(): void {\n if (this.warnedNoTracerProvider) return;\n this.warnedNoTracerProvider = true;\n\n const cause = this.hasCustomTracerProvider\n ? 'The tracerProvider passed to OtelBridge returned a no-op span'\n : 'No OpenTelemetry tracer provider is registered globally';\n\n this.logger.warn(\n `[OtelBridge] ${cause}, so Mastra spans will not be exported through OpenTelemetry. ` +\n 'Register a tracer provider before running agents or workflows (for example, call `sdk.start()` on ' +\n '`NodeSDK` from `@opentelemetry/sdk-node`, or `trace.setGlobalTracerProvider(provider)`), ' +\n 'or pass it directly with `new OtelBridge({ tracerProvider })`. ' +\n `See ${SETUP_DOCS_URL}`,\n );\n }\n\n /**\n * Handle SPAN_ENDED event\n *\n * Retrieves the OTEL span created at SPAN_STARTED, sets all final attributes,\n * events, and status, then ends the span. Cleans up the span map entry.\n */\n private async handleSpanEnded(event: TracingEvent): Promise<void> {\n try {\n const mastraSpan = event.exportedSpan;\n const entry = this.otelSpanMap.get(mastraSpan.id);\n\n if (!entry) {\n // Expected when no tracer provider is registered; warnNoTracerProvider()\n // already reported that once, so don't repeat it for every span.\n this.logger.debug(`[OtelBridge] No OTEL span found for Mastra span [id=${mastraSpan.id}].`);\n return;\n }\n\n // Remove from map immediately to prevent memory leak\n this.otelSpanMap.delete(mastraSpan.id);\n\n if (!this.spanConverter) {\n return;\n }\n\n const { otelSpan } = entry;\n\n this.logger.debug(`[OtelBridge] Ending OTEL span [mastraId=${mastraSpan.id}] [name=${mastraSpan.name}]`);\n\n // Use SpanConverter to get consistent span formatting with otel-exporter\n const readableSpan = await this.spanConverter!.convertSpan(mastraSpan);\n\n // Update span name to match the converter's formatting\n otelSpan.updateName(readableSpan.name);\n\n // Set all attributes from the converter (includes OTEL semantic conventions)\n for (const [key, value] of Object.entries(readableSpan.attributes)) {\n if (value !== undefined && value !== null && typeof value !== 'object') {\n otelSpan.setAttribute(key, value);\n }\n }\n\n // Set status from the converter\n otelSpan.setStatus(readableSpan.status);\n\n // Links set after the span started, such as an MCP tool call's link to the\n // server span that handled it\n const lateLinks = readableSpan.links.filter(link => !entry.startLinks?.has(linkKey(link.context)));\n if (lateLinks.length > 0 && typeof otelSpan.addLinks === 'function') {\n otelSpan.addLinks(lateLinks);\n }\n\n // Add exception events if present\n for (const event of readableSpan.events) {\n if (event.name === 'exception' && event.attributes) {\n const error = new Error(event.attributes['exception.message'] as string);\n otelSpan.recordException(error);\n }\n }\n\n // End the span with the actual end time\n otelSpan.end(mastraSpan.endTime);\n\n this.logger.debug(\n `[OtelBridge] Completed OTEL span [mastraId=${mastraSpan.id}] [traceId=${otelSpan.spanContext().traceId}]`,\n );\n } catch (error) {\n this.logger.error('[OtelBridge] Failed to handle SPAN_ENDED:', error);\n }\n }\n\n /**\n * Release the OTEL span held for a Mastra span that ended without being exported.\n *\n * The OTEL span is deliberately not ended: ending it would hand it to the OTEL\n * span processors and export a span the user's filtering just removed. Dropping\n * the reference is enough, since span processors only queue spans on end.\n */\n releaseSpan(spanId: string): void {\n if (this.otelSpanMap.delete(spanId)) {\n this.logger.debug(\n `[OtelBridge.releaseSpan] Released unexported span [spanId=${spanId}] [mapSize=${this.otelSpanMap.size}]`,\n );\n }\n }\n\n /**\n * Execute a function (sync or async) within the OTEL context of a Mastra span.\n * Retrieves the stored OTEL context for the span and executes the function within it.\n *\n * This is the core implementation used by both executeInContext and executeInContextSync.\n *\n * @param spanId - The ID of the Mastra span to use as context\n * @param fn - The function to execute within the span context\n * @returns The result of the function execution\n */\n private executeWithSpanContext<T>(spanId: string, fn: () => T): T {\n const entry = this.otelSpanMap.get(spanId);\n\n this.logger.debug(\n `[OtelBridge.executeWithSpanContext] spanId=${spanId}, ` +\n `inMap=${!!entry}, ` +\n `storedOtelSpan=${entry?.otelSpan.spanContext().spanId || 'none'}`,\n );\n\n const spanContext = entry?.otelContext;\n if (spanContext) {\n return otelContext.with(spanContext, fn);\n }\n return fn();\n }\n\n /**\n * Execute an async function within the OTEL context of a Mastra span.\n *\n * @param spanId - The ID of the Mastra span to use as context\n * @param fn - The async function to execute within the span context\n * @returns The result of the function execution\n */\n executeInContext<T>(spanId: string, fn: () => Promise<T>): Promise<T> {\n return this.executeWithSpanContext(spanId, fn);\n }\n\n /**\n * Execute a synchronous function within the OTEL context of a Mastra span.\n *\n * @param spanId - The ID of the Mastra span to use as context\n * @param fn - The synchronous function to execute within the span context\n * @returns The result of the function execution\n */\n executeInContextSync<T>(spanId: string, fn: () => T): T {\n return this.executeWithSpanContext(spanId, fn);\n }\n\n /**\n * Force flush any buffered spans without shutting down the bridge.\n *\n * Attempts to flush the underlying OTEL tracer provider if it supports\n * the forceFlush operation. This is useful in serverless environments\n * where you need to ensure all spans are exported before the runtime\n * instance is terminated.\n */\n async flush(): Promise<void> {\n await this.flushProvider(this.tracerProvider, 'tracer');\n await this.flushProvider(this.loggerProvider, 'logger');\n }\n\n private async flushProvider(provider: unknown, label: 'tracer' | 'logger'): Promise<void> {\n try {\n if (\n provider &&\n typeof provider === 'object' &&\n 'forceFlush' in provider &&\n typeof (provider as { forceFlush: unknown }).forceFlush === 'function'\n ) {\n await (provider as { forceFlush: () => Promise<void> }).forceFlush();\n this.logger.debug(`[OtelBridge] Flushed ${label} provider`);\n } else {\n this.logger.debug(\n `[OtelBridge] ${label === 'tracer' ? 'Tracer' : 'Logger'} provider does not support forceFlush`,\n );\n }\n } catch (error) {\n this.logger.error(`[OtelBridge] Failed to flush ${label} provider:`, error);\n }\n }\n\n /**\n * Shutdown the bridge and clean up resources\n */\n async shutdown(): Promise<void> {\n // Flush before shutdown\n await this.flush();\n\n // End any remaining spans\n for (const [spanId, { otelSpan }] of this.otelSpanMap.entries()) {\n this.logger.warn(`[OtelBridge] Force-ending span that was not properly closed [id=${spanId}]`);\n otelSpan.end();\n }\n this.otelSpanMap.clear();\n this.logger.info('[OtelBridge] Shutdown complete');\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["TraceFlags","BaseExporter","otelTrace","otelLogs","TracingEventType","otelContext","SpanConverter"],"sources":["../src/bridge.ts"],"sourcesContent":["/**\n * OpenTelemetry Bridge for Mastra Observability\n *\n * This bridge enables bidirectional integration with OpenTelemetry infrastructure:\n * 1. Reads OTEL trace context from active spans (via AsyncLocalStorage)\n * 2. Creates real OTEL spans when Mastra spans are created\n * 3. Maintains span context for proper parent-child relationships\n * 4. Allows OTEL-instrumented code (DB, HTTP clients) in tools/workflows to have correct parents\n *\n * This creates complete distributed traces where Mastra spans are properly\n * nested within OTEL spans from auto-instrumentation, and any OTEL-instrumented\n * operations within Mastra spans maintain the correct hierarchy.\n */\n\nimport type {\n ObservabilityBridge,\n TracingEvent,\n LogEvent,\n CreateSpanOptions,\n SpanType,\n SpanIds,\n InitExporterOptions,\n SpanLink,\n} from '@mastra/core/observability';\nimport { TracingEventType } from '@mastra/core/observability';\nimport { BaseExporter, getExternalParentId } from '@mastra/observability';\nimport type { BaseExporterConfig } from '@mastra/observability';\nimport { SpanConverter, convertLog, getSpanKind } from '@mastra/otel-exporter';\nimport { trace as otelTrace, context as otelContext, isSpanContextValid, TraceFlags } from '@opentelemetry/api';\nimport type { Span as OtelSpan, Context as OtelContext, Link, TracerProvider, Tracer } from '@opentelemetry/api';\nimport { logs as otelLogs } from '@opentelemetry/api-logs';\nimport type { Logger as OtelLogger, LoggerProvider } from '@opentelemetry/api-logs';\n\nfunction linkKey(link: { traceId: string; spanId: string }): string {\n return `${link.traceId}:${link.spanId}`;\n}\n\n/** OpenTelemetry links for Mastra span links, skipping any whose IDs are not valid OpenTelemetry IDs. */\nfunction toOtelLinks(links: SpanLink[]): Link[] {\n return links\n .map(link => ({ traceId: link.traceId, spanId: link.spanId, traceFlags: TraceFlags.SAMPLED, isRemote: true }))\n .filter(context => isSpanContextValid(context))\n .map(context => ({ context }));\n}\n\nconst SETUP_DOCS_URL = 'https://mastra.ai/reference/observability/tracing/bridges/otel#setup-requirements';\n\nexport type OtelBridgeConfig = BaseExporterConfig & {\n tracerProvider?: TracerProvider;\n loggerProvider?: LoggerProvider;\n};\n\n/**\n * OpenTelemetry Bridge implementation\n *\n * Creates real OTEL spans when Mastra spans are created, maintaining proper\n * context propagation for nested instrumentation.\n *\n * @example\n * ```typescript\n * import { OtelBridge } from '@mastra/otel-bridge';\n * import { Mastra } from '@mastra/core';\n *\n * const mastra = new Mastra({\n * agents: { myAgent },\n * observability: {\n * configs: {\n * default: {\n * serviceName: 'my-service',\n * bridge: new OtelBridge(),\n * }\n * }\n * }\n * });\n * ```\n */\nexport class OtelBridge extends BaseExporter implements ObservabilityBridge {\n name = 'otel';\n private tracerProvider: TracerProvider;\n private loggerProvider: LoggerProvider;\n private otelTracer: Tracer;\n private otelLogger: OtelLogger;\n /** Live OTEL spans by span ID, with the keys of the links they were started with. */\n private otelSpanMap = new Map<string, { otelSpan: OtelSpan; otelContext: OtelContext; startLinks?: Set<string> }>();\n private spanConverter?: SpanConverter;\n private hasCustomTracerProvider: boolean;\n private hasCustomLoggerProvider: boolean;\n private warnedNoTracerProvider = false;\n\n constructor(config: OtelBridgeConfig = {}) {\n super(config);\n this.hasCustomTracerProvider = config.tracerProvider !== undefined;\n this.hasCustomLoggerProvider = config.loggerProvider !== undefined;\n this.tracerProvider = config.tracerProvider ?? otelTrace.getTracerProvider();\n this.otelTracer = this.tracerProvider.getTracer('@mastra/otel-bridge', '1.0.0');\n this.loggerProvider = config.loggerProvider ?? otelLogs.getLoggerProvider();\n this.otelLogger = this.loggerProvider.getLogger('@mastra/otel-bridge', '1.0.0');\n }\n\n /**\n * Handle Mastra tracing events\n *\n * Ships OTEL spans when Mastra spans end.\n * This maintains proper span hierarchy and allows OTEL-instrumented code within\n * Mastra spans to have correct parent-child relationships.\n * Note: OTEL spans are created when registerSpan is called when the span is first created.\n */\n protected async _exportTracingEvent(event: TracingEvent): Promise<void> {\n if (event.type === TracingEventType.SPAN_ENDED) {\n await this.handleSpanEnded(event);\n }\n }\n\n /**\n * Forward Mastra log events into the globally-registered OTEL LoggerProvider.\n *\n * If the user has not registered a LoggerProvider (e.g. via @opentelemetry/sdk-logs\n * or NodeSDK's logRecordProcessor option), the API returns a no-op logger and\n * emit() is a silent no-op — the bridge degrades gracefully.\n *\n * Trace correlation:\n * - If the log carries a spanId we have an OTEL span for, emit under that span's\n * stored context so the log nests beneath it in the trace.\n * - Else if the log carries traceId+spanId, attach a SpanContext built from those\n * IDs so backends still correlate by ID.\n * - Else emit under whatever context is currently active.\n */\n async onLogEvent(event: LogEvent): Promise<void> {\n if (this.isDisabled) return;\n\n try {\n const params = convertLog(event.log);\n\n const attributes = { ...params.attributes };\n if (params.traceId) attributes['mastra.traceId'] = params.traceId;\n if (params.spanId) attributes['mastra.spanId'] = params.spanId;\n\n const logContext = this.resolveLogContext(params.traceId, params.spanId);\n\n this.otelLogger.emit({\n timestamp: params.timestamp,\n severityNumber: params.severityNumber,\n severityText: params.severityText,\n body: params.body,\n attributes,\n context: logContext,\n });\n } catch (error) {\n this.logger.error('[OtelBridge] Failed to emit log:', error);\n }\n }\n\n /**\n * Pick the OTEL Context to emit a log under so trace correlation is correct.\n */\n private resolveLogContext(traceId?: string, spanId?: string): OtelContext {\n // 1. Prefer the stored OTEL context for the originating Mastra span.\n if (spanId) {\n const entry = this.otelSpanMap.get(spanId);\n if (entry) return entry.otelContext;\n }\n\n // 2. Fall back to a context with a span context built from the raw IDs,\n // but only when both IDs form a valid W3C span context. Injecting\n // malformed IDs would surface as garbage trace links downstream.\n if (traceId && spanId) {\n const candidate = {\n traceId,\n spanId,\n traceFlags: TraceFlags.SAMPLED,\n isRemote: false,\n };\n if (isSpanContextValid(candidate)) {\n return otelTrace.setSpanContext(otelContext.active(), candidate);\n }\n }\n\n // 3. Fall through to whatever is currently active.\n return otelContext.active();\n }\n\n /**\n * Initialize with tracing configuration\n */\n init(options: InitExporterOptions) {\n this.spanConverter = new SpanConverter({\n packageName: '@mastra/otel-bridge',\n serviceName: options.config?.serviceName,\n format: 'GenAI_v1_38_0',\n });\n }\n\n /**\n * Create a span in the bridge's tracing system.\n * Called during Mastra span construction to get bridge-generated identifiers.\n *\n * @param options - Span creation options from Mastra\n * @returns Span identifiers (spanId, traceId, parentSpanId) from bridge, or undefined if creation fails\n */\n createSpan(options: CreateSpanOptions<SpanType>): SpanIds | undefined {\n try {\n // Determine parent context\n let parentOtelContext = otelContext.active();\n let usedPersistedParent = false;\n\n // Get external parent ID (walks up chain to find non-internal parent)\n const externalParentId = getExternalParentId(options);\n if (externalParentId) {\n // Look up external parent's OTEL span from map\n const parentEntry = this.otelSpanMap.get(externalParentId);\n if (parentEntry) {\n parentOtelContext = parentEntry.otelContext;\n }\n } else if (options.traceId && options.parentSpanId) {\n // Root spans restored from persisted state (e.g. workflow suspend/resume)\n // carry the original trace ID and parent span ID, but the parent OTEL span\n // is no longer alive — it ended when the run suspended, possibly in another\n // process. Parent under a remote span context built from the persisted IDs\n // so the span continues the original trace instead of joining whatever\n // context happens to be active. Skip malformed IDs; injecting them would\n // surface as garbage trace links downstream.\n const candidate = {\n traceId: options.traceId,\n spanId: options.parentSpanId,\n traceFlags: TraceFlags.SAMPLED,\n isRemote: true,\n };\n if (isSpanContextValid(candidate)) {\n parentOtelContext = otelTrace.setSpanContext(parentOtelContext, candidate);\n usedPersistedParent = true;\n }\n }\n\n // Create OTEL span with SpanKind (must be set at creation, immutable)\n const otelSpan = this.otelTracer.startSpan(\n options.name,\n {\n kind: getSpanKind(options.type),\n ...(options.startTime ? { startTime: options.startTime } : {}),\n ...(options.links?.length ? { links: toOtelLinks(options.links) } : {}),\n },\n parentOtelContext,\n );\n\n // Create context with this span active\n const spanContext = otelTrace.setSpan(parentOtelContext, otelSpan);\n\n // Get OTEL span identifiers\n const otelSpanContext = otelSpan.spanContext();\n\n // If no OTEL SDK is registered, the global tracer is a no-op. With no\n // parent it returns a span with an invalid (all-zero) span context; with\n // a parent in the context (e.g. an outer span from another, unregistered\n // provider) it returns a span wrapping the parent's span context. Either\n // way, returning those IDs would collide across every Mastra span and\n // break downstream exporters. Bail out so DefaultSpan falls through to\n // its own ID generator. A real tracer always mints a fresh span ID, even\n // for spans its sampler drops.\n const parentOtelSpanContext = otelTrace.getSpanContext(parentOtelContext);\n if (\n !isSpanContextValid(otelSpanContext) ||\n (parentOtelSpanContext !== undefined && otelSpanContext.spanId === parentOtelSpanContext.spanId)\n ) {\n this.warnNoTracerProvider();\n // End the span we just started so its lifecycle stays clean on\n // providers that do track non-recording spans.\n otelSpan.end();\n return undefined;\n }\n\n const spanId = otelSpanContext.spanId;\n const traceId = otelSpanContext.traceId;\n\n // Store for later retrieval (for executeWithSpanContext and event handling)\n this.otelSpanMap.set(spanId, {\n otelSpan,\n otelContext: spanContext,\n ...(options.links?.length ? { startLinks: new Set(options.links.map(linkKey)) } : {}),\n });\n\n // Get parentSpanId from parent context if available\n const parentSpan = otelTrace.getSpan(parentOtelContext);\n const parentSpanContext = parentSpan?.spanContext();\n const parentSpanId =\n parentSpanContext && isSpanContextValid(parentSpanContext) ? parentSpanContext.spanId : undefined;\n\n // Declare which kind of parent was used: a span this bridge created for\n // a Mastra span is part of the Mastra trace, while anything else in the\n // ambient OTEL context belongs to the external tracing system. A parent\n // restored from persisted state is a Mastra span in storage — it ended at\n // suspend (possibly in another process), so it is absent from the live map.\n const parentIsMastraSpan =\n parentSpanId !== undefined && (this.otelSpanMap.has(parentSpanId) || usedPersistedParent);\n\n this.logger.debug(\n `[OtelBridge.createSpan] Created span [spanId=${spanId}] [traceId=${traceId}] ` +\n `[parentSpanId=${parentSpanId}] [parentIsMastraSpan=${parentIsMastraSpan}] ` +\n `[type=${options.type}] [mapSize=${this.otelSpanMap.size}]`,\n );\n\n return {\n spanId,\n traceId,\n ...(parentIsMastraSpan ? { parentSpanId } : { externalParentSpanId: parentSpanId }),\n };\n } catch (error) {\n this.logger.error('[OtelBridge] Failed to create span:', error);\n return undefined;\n }\n }\n\n /**\n * Warn once per bridge that no tracer provider is producing spans. Checked\n * lazily on span creation rather than at startup, because the OTEL SDK is\n * often registered after the bridge is constructed.\n */\n private warnNoTracerProvider(): void {\n if (this.warnedNoTracerProvider) return;\n this.warnedNoTracerProvider = true;\n\n const cause = this.hasCustomTracerProvider\n ? 'The tracerProvider passed to OtelBridge returned a no-op span'\n : 'No OpenTelemetry tracer provider is registered globally';\n\n this.logger.warn(\n `[OtelBridge] ${cause}, so Mastra spans will not be exported through OpenTelemetry. ` +\n 'Register a tracer provider before running agents or workflows (for example, call `sdk.start()` on ' +\n '`NodeSDK` from `@opentelemetry/sdk-node`, or `trace.setGlobalTracerProvider(provider)`), ' +\n 'or pass it directly with `new OtelBridge({ tracerProvider })`. ' +\n `See ${SETUP_DOCS_URL}`,\n );\n }\n\n /**\n * Handle SPAN_ENDED event\n *\n * Retrieves the OTEL span created at SPAN_STARTED, sets all final attributes,\n * events, and status, then ends the span. Cleans up the span map entry.\n */\n private async handleSpanEnded(event: TracingEvent): Promise<void> {\n try {\n const mastraSpan = event.exportedSpan;\n const entry = this.otelSpanMap.get(mastraSpan.id);\n\n if (!entry) {\n // Expected when no tracer provider is registered; warnNoTracerProvider()\n // already reported that once, so don't repeat it for every span.\n this.logger.debug(`[OtelBridge] No OTEL span found for Mastra span [id=${mastraSpan.id}].`);\n return;\n }\n\n // Remove from map immediately to prevent memory leak\n this.otelSpanMap.delete(mastraSpan.id);\n\n if (!this.spanConverter) {\n return;\n }\n\n const { otelSpan } = entry;\n\n this.logger.debug(`[OtelBridge] Ending OTEL span [mastraId=${mastraSpan.id}] [name=${mastraSpan.name}]`);\n\n // Use SpanConverter to get consistent span formatting with otel-exporter\n const readableSpan = await this.spanConverter!.convertSpan(mastraSpan);\n\n // Update span name to match the converter's formatting\n otelSpan.updateName(readableSpan.name);\n\n // Set all attributes from the converter (includes OTEL semantic conventions)\n for (const [key, value] of Object.entries(readableSpan.attributes)) {\n if (value !== undefined && value !== null && typeof value !== 'object') {\n otelSpan.setAttribute(key, value);\n }\n }\n\n // Set status from the converter\n otelSpan.setStatus(readableSpan.status);\n\n // Links set after the span started, such as an MCP tool call's link to the\n // server span that handled it\n const lateLinks = readableSpan.links.filter(link => !entry.startLinks?.has(linkKey(link.context)));\n if (lateLinks.length > 0 && typeof otelSpan.addLinks === 'function') {\n otelSpan.addLinks(lateLinks);\n }\n\n // Add exception events if present\n for (const event of readableSpan.events) {\n if (event.name === 'exception' && event.attributes) {\n const error = new Error(event.attributes['exception.message'] as string);\n otelSpan.recordException(error);\n }\n }\n\n // End the span with the actual end time\n otelSpan.end(mastraSpan.endTime);\n\n this.logger.debug(\n `[OtelBridge] Completed OTEL span [mastraId=${mastraSpan.id}] [traceId=${otelSpan.spanContext().traceId}]`,\n );\n } catch (error) {\n this.logger.error('[OtelBridge] Failed to handle SPAN_ENDED:', error);\n }\n }\n\n /**\n * Release the OTEL span held for a Mastra span that ended without being exported.\n *\n * The OTEL span is deliberately not ended: ending it would hand it to the OTEL\n * span processors and export a span the user's filtering just removed. Dropping\n * the reference is enough, since span processors only queue spans on end.\n */\n releaseSpan(spanId: string): void {\n if (this.otelSpanMap.delete(spanId)) {\n this.logger.debug(\n `[OtelBridge.releaseSpan] Released unexported span [spanId=${spanId}] [mapSize=${this.otelSpanMap.size}]`,\n );\n }\n }\n\n /**\n * Execute a function (sync or async) within the OTEL context of a Mastra span.\n * Retrieves the stored OTEL context for the span and executes the function within it.\n *\n * This is the core implementation used by both executeInContext and executeInContextSync.\n *\n * @param spanId - The ID of the Mastra span to use as context\n * @param fn - The function to execute within the span context\n * @returns The result of the function execution\n */\n private executeWithSpanContext<T>(spanId: string, fn: () => T): T {\n const entry = this.otelSpanMap.get(spanId);\n\n this.logger.debug(\n `[OtelBridge.executeWithSpanContext] spanId=${spanId}, ` +\n `inMap=${!!entry}, ` +\n `storedOtelSpan=${entry?.otelSpan.spanContext().spanId || 'none'}`,\n );\n\n const spanContext = entry?.otelContext;\n if (spanContext) {\n return otelContext.with(spanContext, fn);\n }\n return fn();\n }\n\n /**\n * Execute an async function within the OTEL context of a Mastra span.\n *\n * @param spanId - The ID of the Mastra span to use as context\n * @param fn - The async function to execute within the span context\n * @returns The result of the function execution\n */\n executeInContext<T>(spanId: string, fn: () => Promise<T>): Promise<T> {\n return this.executeWithSpanContext(spanId, fn);\n }\n\n /**\n * Execute a synchronous function within the OTEL context of a Mastra span.\n *\n * @param spanId - The ID of the Mastra span to use as context\n * @param fn - The synchronous function to execute within the span context\n * @returns The result of the function execution\n */\n executeInContextSync<T>(spanId: string, fn: () => T): T {\n return this.executeWithSpanContext(spanId, fn);\n }\n\n /**\n * Force flush any buffered spans without shutting down the bridge.\n *\n * Attempts to flush the underlying OTEL tracer provider if it supports\n * the forceFlush operation. This is useful in serverless environments\n * where you need to ensure all spans are exported before the runtime\n * instance is terminated.\n */\n async flush(): Promise<void> {\n // Re-resolve the global providers on every flush: the OTEL SDK is often\n // registered after the bridge is constructed, and `otelLogs.getLoggerProvider()`\n // only returns the real provider once one has been registered.\n const tracerProvider = this.hasCustomTracerProvider ? this.tracerProvider : otelTrace.getTracerProvider();\n const loggerProvider = this.hasCustomLoggerProvider ? this.loggerProvider : otelLogs.getLoggerProvider();\n await this.flushProvider(tracerProvider, 'tracer');\n await this.flushProvider(loggerProvider, 'logger');\n }\n\n private async flushProvider(provider: unknown, label: 'tracer' | 'logger'): Promise<void> {\n try {\n // `trace.getTracerProvider()` returns the API's ProxyTracerProvider once a\n // global provider is registered (e.g. by `NodeSDK.start()`). The proxy has\n // no forceFlush of its own; the SDK provider behind it does.\n if (\n provider &&\n typeof provider === 'object' &&\n 'getDelegate' in provider &&\n typeof (provider as { getDelegate: unknown }).getDelegate === 'function'\n ) {\n provider = (provider as { getDelegate: () => unknown }).getDelegate();\n }\n if (\n provider &&\n typeof provider === 'object' &&\n 'forceFlush' in provider &&\n typeof (provider as { forceFlush: unknown }).forceFlush === 'function'\n ) {\n await (provider as { forceFlush: () => Promise<void> }).forceFlush();\n this.logger.debug(`[OtelBridge] Flushed ${label} provider`);\n } else {\n this.logger.debug(\n `[OtelBridge] ${label === 'tracer' ? 'Tracer' : 'Logger'} provider does not support forceFlush`,\n );\n }\n } catch (error) {\n this.logger.error(`[OtelBridge] Failed to flush ${label} provider:`, error);\n }\n }\n\n /**\n * Shutdown the bridge and clean up resources\n */\n async shutdown(): Promise<void> {\n // End any remaining spans first so the flush below exports them too\n for (const [spanId, { otelSpan }] of this.otelSpanMap.entries()) {\n this.logger.warn(`[OtelBridge] Force-ending span that was not properly closed [id=${spanId}]`);\n otelSpan.end();\n }\n this.otelSpanMap.clear();\n\n await this.flush();\n this.logger.info('[OtelBridge] Shutdown complete');\n }\n}\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -51,10 +51,12 @@ var OtelBridge = class extends BaseExporter {
51
51
  otelSpanMap = /* @__PURE__ */ new Map();
52
52
  spanConverter;
53
53
  hasCustomTracerProvider;
54
+ hasCustomLoggerProvider;
54
55
  warnedNoTracerProvider = false;
55
56
  constructor(config = {}) {
56
57
  super(config);
57
58
  this.hasCustomTracerProvider = config.tracerProvider !== void 0;
59
+ this.hasCustomLoggerProvider = config.loggerProvider !== void 0;
58
60
  this.tracerProvider = config.tracerProvider ?? trace.getTracerProvider();
59
61
  this.otelTracer = this.tracerProvider.getTracer("@mastra/otel-bridge", "1.0.0");
60
62
  this.loggerProvider = config.loggerProvider ?? logs.getLoggerProvider();
@@ -296,11 +298,14 @@ var OtelBridge = class extends BaseExporter {
296
298
  * instance is terminated.
297
299
  */
298
300
  async flush() {
299
- await this.flushProvider(this.tracerProvider, "tracer");
300
- await this.flushProvider(this.loggerProvider, "logger");
301
+ const tracerProvider = this.hasCustomTracerProvider ? this.tracerProvider : trace.getTracerProvider();
302
+ const loggerProvider = this.hasCustomLoggerProvider ? this.loggerProvider : logs.getLoggerProvider();
303
+ await this.flushProvider(tracerProvider, "tracer");
304
+ await this.flushProvider(loggerProvider, "logger");
301
305
  }
302
306
  async flushProvider(provider, label) {
303
307
  try {
308
+ if (provider && typeof provider === "object" && "getDelegate" in provider && typeof provider.getDelegate === "function") provider = provider.getDelegate();
304
309
  if (provider && typeof provider === "object" && "forceFlush" in provider && typeof provider.forceFlush === "function") {
305
310
  await provider.forceFlush();
306
311
  this.logger.debug(`[OtelBridge] Flushed ${label} provider`);
@@ -313,12 +318,12 @@ var OtelBridge = class extends BaseExporter {
313
318
  * Shutdown the bridge and clean up resources
314
319
  */
315
320
  async shutdown() {
316
- await this.flush();
317
321
  for (const [spanId, { otelSpan }] of this.otelSpanMap.entries()) {
318
322
  this.logger.warn(`[OtelBridge] Force-ending span that was not properly closed [id=${spanId}]`);
319
323
  otelSpan.end();
320
324
  }
321
325
  this.otelSpanMap.clear();
326
+ await this.flush();
322
327
  this.logger.info("[OtelBridge] Shutdown complete");
323
328
  }
324
329
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["otelTrace","otelLogs","otelContext"],"sources":["../src/bridge.ts"],"sourcesContent":["/**\n * OpenTelemetry Bridge for Mastra Observability\n *\n * This bridge enables bidirectional integration with OpenTelemetry infrastructure:\n * 1. Reads OTEL trace context from active spans (via AsyncLocalStorage)\n * 2. Creates real OTEL spans when Mastra spans are created\n * 3. Maintains span context for proper parent-child relationships\n * 4. Allows OTEL-instrumented code (DB, HTTP clients) in tools/workflows to have correct parents\n *\n * This creates complete distributed traces where Mastra spans are properly\n * nested within OTEL spans from auto-instrumentation, and any OTEL-instrumented\n * operations within Mastra spans maintain the correct hierarchy.\n */\n\nimport type {\n ObservabilityBridge,\n TracingEvent,\n LogEvent,\n CreateSpanOptions,\n SpanType,\n SpanIds,\n InitExporterOptions,\n SpanLink,\n} from '@mastra/core/observability';\nimport { TracingEventType } from '@mastra/core/observability';\nimport { BaseExporter, getExternalParentId } from '@mastra/observability';\nimport type { BaseExporterConfig } from '@mastra/observability';\nimport { SpanConverter, convertLog, getSpanKind } from '@mastra/otel-exporter';\nimport { trace as otelTrace, context as otelContext, isSpanContextValid, TraceFlags } from '@opentelemetry/api';\nimport type { Span as OtelSpan, Context as OtelContext, Link, TracerProvider, Tracer } from '@opentelemetry/api';\nimport { logs as otelLogs } from '@opentelemetry/api-logs';\nimport type { Logger as OtelLogger, LoggerProvider } from '@opentelemetry/api-logs';\n\nfunction linkKey(link: { traceId: string; spanId: string }): string {\n return `${link.traceId}:${link.spanId}`;\n}\n\n/** OpenTelemetry links for Mastra span links, skipping any whose IDs are not valid OpenTelemetry IDs. */\nfunction toOtelLinks(links: SpanLink[]): Link[] {\n return links\n .map(link => ({ traceId: link.traceId, spanId: link.spanId, traceFlags: TraceFlags.SAMPLED, isRemote: true }))\n .filter(context => isSpanContextValid(context))\n .map(context => ({ context }));\n}\n\nconst SETUP_DOCS_URL = 'https://mastra.ai/reference/observability/tracing/bridges/otel#setup-requirements';\n\nexport type OtelBridgeConfig = BaseExporterConfig & {\n tracerProvider?: TracerProvider;\n loggerProvider?: LoggerProvider;\n};\n\n/**\n * OpenTelemetry Bridge implementation\n *\n * Creates real OTEL spans when Mastra spans are created, maintaining proper\n * context propagation for nested instrumentation.\n *\n * @example\n * ```typescript\n * import { OtelBridge } from '@mastra/otel-bridge';\n * import { Mastra } from '@mastra/core';\n *\n * const mastra = new Mastra({\n * agents: { myAgent },\n * observability: {\n * configs: {\n * default: {\n * serviceName: 'my-service',\n * bridge: new OtelBridge(),\n * }\n * }\n * }\n * });\n * ```\n */\nexport class OtelBridge extends BaseExporter implements ObservabilityBridge {\n name = 'otel';\n private tracerProvider: TracerProvider;\n private loggerProvider: LoggerProvider;\n private otelTracer: Tracer;\n private otelLogger: OtelLogger;\n /** Live OTEL spans by span ID, with the keys of the links they were started with. */\n private otelSpanMap = new Map<string, { otelSpan: OtelSpan; otelContext: OtelContext; startLinks?: Set<string> }>();\n private spanConverter?: SpanConverter;\n private hasCustomTracerProvider: boolean;\n private warnedNoTracerProvider = false;\n\n constructor(config: OtelBridgeConfig = {}) {\n super(config);\n this.hasCustomTracerProvider = config.tracerProvider !== undefined;\n this.tracerProvider = config.tracerProvider ?? otelTrace.getTracerProvider();\n this.otelTracer = this.tracerProvider.getTracer('@mastra/otel-bridge', '1.0.0');\n this.loggerProvider = config.loggerProvider ?? otelLogs.getLoggerProvider();\n this.otelLogger = this.loggerProvider.getLogger('@mastra/otel-bridge', '1.0.0');\n }\n\n /**\n * Handle Mastra tracing events\n *\n * Ships OTEL spans when Mastra spans end.\n * This maintains proper span hierarchy and allows OTEL-instrumented code within\n * Mastra spans to have correct parent-child relationships.\n * Note: OTEL spans are created when registerSpan is called when the span is first created.\n */\n protected async _exportTracingEvent(event: TracingEvent): Promise<void> {\n if (event.type === TracingEventType.SPAN_ENDED) {\n await this.handleSpanEnded(event);\n }\n }\n\n /**\n * Forward Mastra log events into the globally-registered OTEL LoggerProvider.\n *\n * If the user has not registered a LoggerProvider (e.g. via @opentelemetry/sdk-logs\n * or NodeSDK's logRecordProcessor option), the API returns a no-op logger and\n * emit() is a silent no-op — the bridge degrades gracefully.\n *\n * Trace correlation:\n * - If the log carries a spanId we have an OTEL span for, emit under that span's\n * stored context so the log nests beneath it in the trace.\n * - Else if the log carries traceId+spanId, attach a SpanContext built from those\n * IDs so backends still correlate by ID.\n * - Else emit under whatever context is currently active.\n */\n async onLogEvent(event: LogEvent): Promise<void> {\n if (this.isDisabled) return;\n\n try {\n const params = convertLog(event.log);\n\n const attributes = { ...params.attributes };\n if (params.traceId) attributes['mastra.traceId'] = params.traceId;\n if (params.spanId) attributes['mastra.spanId'] = params.spanId;\n\n const logContext = this.resolveLogContext(params.traceId, params.spanId);\n\n this.otelLogger.emit({\n timestamp: params.timestamp,\n severityNumber: params.severityNumber,\n severityText: params.severityText,\n body: params.body,\n attributes,\n context: logContext,\n });\n } catch (error) {\n this.logger.error('[OtelBridge] Failed to emit log:', error);\n }\n }\n\n /**\n * Pick the OTEL Context to emit a log under so trace correlation is correct.\n */\n private resolveLogContext(traceId?: string, spanId?: string): OtelContext {\n // 1. Prefer the stored OTEL context for the originating Mastra span.\n if (spanId) {\n const entry = this.otelSpanMap.get(spanId);\n if (entry) return entry.otelContext;\n }\n\n // 2. Fall back to a context with a span context built from the raw IDs,\n // but only when both IDs form a valid W3C span context. Injecting\n // malformed IDs would surface as garbage trace links downstream.\n if (traceId && spanId) {\n const candidate = {\n traceId,\n spanId,\n traceFlags: TraceFlags.SAMPLED,\n isRemote: false,\n };\n if (isSpanContextValid(candidate)) {\n return otelTrace.setSpanContext(otelContext.active(), candidate);\n }\n }\n\n // 3. Fall through to whatever is currently active.\n return otelContext.active();\n }\n\n /**\n * Initialize with tracing configuration\n */\n init(options: InitExporterOptions) {\n this.spanConverter = new SpanConverter({\n packageName: '@mastra/otel-bridge',\n serviceName: options.config?.serviceName,\n format: 'GenAI_v1_38_0',\n });\n }\n\n /**\n * Create a span in the bridge's tracing system.\n * Called during Mastra span construction to get bridge-generated identifiers.\n *\n * @param options - Span creation options from Mastra\n * @returns Span identifiers (spanId, traceId, parentSpanId) from bridge, or undefined if creation fails\n */\n createSpan(options: CreateSpanOptions<SpanType>): SpanIds | undefined {\n try {\n // Determine parent context\n let parentOtelContext = otelContext.active();\n let usedPersistedParent = false;\n\n // Get external parent ID (walks up chain to find non-internal parent)\n const externalParentId = getExternalParentId(options);\n if (externalParentId) {\n // Look up external parent's OTEL span from map\n const parentEntry = this.otelSpanMap.get(externalParentId);\n if (parentEntry) {\n parentOtelContext = parentEntry.otelContext;\n }\n } else if (options.traceId && options.parentSpanId) {\n // Root spans restored from persisted state (e.g. workflow suspend/resume)\n // carry the original trace ID and parent span ID, but the parent OTEL span\n // is no longer alive — it ended when the run suspended, possibly in another\n // process. Parent under a remote span context built from the persisted IDs\n // so the span continues the original trace instead of joining whatever\n // context happens to be active. Skip malformed IDs; injecting them would\n // surface as garbage trace links downstream.\n const candidate = {\n traceId: options.traceId,\n spanId: options.parentSpanId,\n traceFlags: TraceFlags.SAMPLED,\n isRemote: true,\n };\n if (isSpanContextValid(candidate)) {\n parentOtelContext = otelTrace.setSpanContext(parentOtelContext, candidate);\n usedPersistedParent = true;\n }\n }\n\n // Create OTEL span with SpanKind (must be set at creation, immutable)\n const otelSpan = this.otelTracer.startSpan(\n options.name,\n {\n kind: getSpanKind(options.type),\n ...(options.startTime ? { startTime: options.startTime } : {}),\n ...(options.links?.length ? { links: toOtelLinks(options.links) } : {}),\n },\n parentOtelContext,\n );\n\n // Create context with this span active\n const spanContext = otelTrace.setSpan(parentOtelContext, otelSpan);\n\n // Get OTEL span identifiers\n const otelSpanContext = otelSpan.spanContext();\n\n // If no OTEL SDK is registered, the global tracer is a no-op. With no\n // parent it returns a span with an invalid (all-zero) span context; with\n // a parent in the context (e.g. an outer span from another, unregistered\n // provider) it returns a span wrapping the parent's span context. Either\n // way, returning those IDs would collide across every Mastra span and\n // break downstream exporters. Bail out so DefaultSpan falls through to\n // its own ID generator. A real tracer always mints a fresh span ID, even\n // for spans its sampler drops.\n const parentOtelSpanContext = otelTrace.getSpanContext(parentOtelContext);\n if (\n !isSpanContextValid(otelSpanContext) ||\n (parentOtelSpanContext !== undefined && otelSpanContext.spanId === parentOtelSpanContext.spanId)\n ) {\n this.warnNoTracerProvider();\n // End the span we just started so its lifecycle stays clean on\n // providers that do track non-recording spans.\n otelSpan.end();\n return undefined;\n }\n\n const spanId = otelSpanContext.spanId;\n const traceId = otelSpanContext.traceId;\n\n // Store for later retrieval (for executeWithSpanContext and event handling)\n this.otelSpanMap.set(spanId, {\n otelSpan,\n otelContext: spanContext,\n ...(options.links?.length ? { startLinks: new Set(options.links.map(linkKey)) } : {}),\n });\n\n // Get parentSpanId from parent context if available\n const parentSpan = otelTrace.getSpan(parentOtelContext);\n const parentSpanContext = parentSpan?.spanContext();\n const parentSpanId =\n parentSpanContext && isSpanContextValid(parentSpanContext) ? parentSpanContext.spanId : undefined;\n\n // Declare which kind of parent was used: a span this bridge created for\n // a Mastra span is part of the Mastra trace, while anything else in the\n // ambient OTEL context belongs to the external tracing system. A parent\n // restored from persisted state is a Mastra span in storage — it ended at\n // suspend (possibly in another process), so it is absent from the live map.\n const parentIsMastraSpan =\n parentSpanId !== undefined && (this.otelSpanMap.has(parentSpanId) || usedPersistedParent);\n\n this.logger.debug(\n `[OtelBridge.createSpan] Created span [spanId=${spanId}] [traceId=${traceId}] ` +\n `[parentSpanId=${parentSpanId}] [parentIsMastraSpan=${parentIsMastraSpan}] ` +\n `[type=${options.type}] [mapSize=${this.otelSpanMap.size}]`,\n );\n\n return {\n spanId,\n traceId,\n ...(parentIsMastraSpan ? { parentSpanId } : { externalParentSpanId: parentSpanId }),\n };\n } catch (error) {\n this.logger.error('[OtelBridge] Failed to create span:', error);\n return undefined;\n }\n }\n\n /**\n * Warn once per bridge that no tracer provider is producing spans. Checked\n * lazily on span creation rather than at startup, because the OTEL SDK is\n * often registered after the bridge is constructed.\n */\n private warnNoTracerProvider(): void {\n if (this.warnedNoTracerProvider) return;\n this.warnedNoTracerProvider = true;\n\n const cause = this.hasCustomTracerProvider\n ? 'The tracerProvider passed to OtelBridge returned a no-op span'\n : 'No OpenTelemetry tracer provider is registered globally';\n\n this.logger.warn(\n `[OtelBridge] ${cause}, so Mastra spans will not be exported through OpenTelemetry. ` +\n 'Register a tracer provider before running agents or workflows (for example, call `sdk.start()` on ' +\n '`NodeSDK` from `@opentelemetry/sdk-node`, or `trace.setGlobalTracerProvider(provider)`), ' +\n 'or pass it directly with `new OtelBridge({ tracerProvider })`. ' +\n `See ${SETUP_DOCS_URL}`,\n );\n }\n\n /**\n * Handle SPAN_ENDED event\n *\n * Retrieves the OTEL span created at SPAN_STARTED, sets all final attributes,\n * events, and status, then ends the span. Cleans up the span map entry.\n */\n private async handleSpanEnded(event: TracingEvent): Promise<void> {\n try {\n const mastraSpan = event.exportedSpan;\n const entry = this.otelSpanMap.get(mastraSpan.id);\n\n if (!entry) {\n // Expected when no tracer provider is registered; warnNoTracerProvider()\n // already reported that once, so don't repeat it for every span.\n this.logger.debug(`[OtelBridge] No OTEL span found for Mastra span [id=${mastraSpan.id}].`);\n return;\n }\n\n // Remove from map immediately to prevent memory leak\n this.otelSpanMap.delete(mastraSpan.id);\n\n if (!this.spanConverter) {\n return;\n }\n\n const { otelSpan } = entry;\n\n this.logger.debug(`[OtelBridge] Ending OTEL span [mastraId=${mastraSpan.id}] [name=${mastraSpan.name}]`);\n\n // Use SpanConverter to get consistent span formatting with otel-exporter\n const readableSpan = await this.spanConverter!.convertSpan(mastraSpan);\n\n // Update span name to match the converter's formatting\n otelSpan.updateName(readableSpan.name);\n\n // Set all attributes from the converter (includes OTEL semantic conventions)\n for (const [key, value] of Object.entries(readableSpan.attributes)) {\n if (value !== undefined && value !== null && typeof value !== 'object') {\n otelSpan.setAttribute(key, value);\n }\n }\n\n // Set status from the converter\n otelSpan.setStatus(readableSpan.status);\n\n // Links set after the span started, such as an MCP tool call's link to the\n // server span that handled it\n const lateLinks = readableSpan.links.filter(link => !entry.startLinks?.has(linkKey(link.context)));\n if (lateLinks.length > 0 && typeof otelSpan.addLinks === 'function') {\n otelSpan.addLinks(lateLinks);\n }\n\n // Add exception events if present\n for (const event of readableSpan.events) {\n if (event.name === 'exception' && event.attributes) {\n const error = new Error(event.attributes['exception.message'] as string);\n otelSpan.recordException(error);\n }\n }\n\n // End the span with the actual end time\n otelSpan.end(mastraSpan.endTime);\n\n this.logger.debug(\n `[OtelBridge] Completed OTEL span [mastraId=${mastraSpan.id}] [traceId=${otelSpan.spanContext().traceId}]`,\n );\n } catch (error) {\n this.logger.error('[OtelBridge] Failed to handle SPAN_ENDED:', error);\n }\n }\n\n /**\n * Release the OTEL span held for a Mastra span that ended without being exported.\n *\n * The OTEL span is deliberately not ended: ending it would hand it to the OTEL\n * span processors and export a span the user's filtering just removed. Dropping\n * the reference is enough, since span processors only queue spans on end.\n */\n releaseSpan(spanId: string): void {\n if (this.otelSpanMap.delete(spanId)) {\n this.logger.debug(\n `[OtelBridge.releaseSpan] Released unexported span [spanId=${spanId}] [mapSize=${this.otelSpanMap.size}]`,\n );\n }\n }\n\n /**\n * Execute a function (sync or async) within the OTEL context of a Mastra span.\n * Retrieves the stored OTEL context for the span and executes the function within it.\n *\n * This is the core implementation used by both executeInContext and executeInContextSync.\n *\n * @param spanId - The ID of the Mastra span to use as context\n * @param fn - The function to execute within the span context\n * @returns The result of the function execution\n */\n private executeWithSpanContext<T>(spanId: string, fn: () => T): T {\n const entry = this.otelSpanMap.get(spanId);\n\n this.logger.debug(\n `[OtelBridge.executeWithSpanContext] spanId=${spanId}, ` +\n `inMap=${!!entry}, ` +\n `storedOtelSpan=${entry?.otelSpan.spanContext().spanId || 'none'}`,\n );\n\n const spanContext = entry?.otelContext;\n if (spanContext) {\n return otelContext.with(spanContext, fn);\n }\n return fn();\n }\n\n /**\n * Execute an async function within the OTEL context of a Mastra span.\n *\n * @param spanId - The ID of the Mastra span to use as context\n * @param fn - The async function to execute within the span context\n * @returns The result of the function execution\n */\n executeInContext<T>(spanId: string, fn: () => Promise<T>): Promise<T> {\n return this.executeWithSpanContext(spanId, fn);\n }\n\n /**\n * Execute a synchronous function within the OTEL context of a Mastra span.\n *\n * @param spanId - The ID of the Mastra span to use as context\n * @param fn - The synchronous function to execute within the span context\n * @returns The result of the function execution\n */\n executeInContextSync<T>(spanId: string, fn: () => T): T {\n return this.executeWithSpanContext(spanId, fn);\n }\n\n /**\n * Force flush any buffered spans without shutting down the bridge.\n *\n * Attempts to flush the underlying OTEL tracer provider if it supports\n * the forceFlush operation. This is useful in serverless environments\n * where you need to ensure all spans are exported before the runtime\n * instance is terminated.\n */\n async flush(): Promise<void> {\n await this.flushProvider(this.tracerProvider, 'tracer');\n await this.flushProvider(this.loggerProvider, 'logger');\n }\n\n private async flushProvider(provider: unknown, label: 'tracer' | 'logger'): Promise<void> {\n try {\n if (\n provider &&\n typeof provider === 'object' &&\n 'forceFlush' in provider &&\n typeof (provider as { forceFlush: unknown }).forceFlush === 'function'\n ) {\n await (provider as { forceFlush: () => Promise<void> }).forceFlush();\n this.logger.debug(`[OtelBridge] Flushed ${label} provider`);\n } else {\n this.logger.debug(\n `[OtelBridge] ${label === 'tracer' ? 'Tracer' : 'Logger'} provider does not support forceFlush`,\n );\n }\n } catch (error) {\n this.logger.error(`[OtelBridge] Failed to flush ${label} provider:`, error);\n }\n }\n\n /**\n * Shutdown the bridge and clean up resources\n */\n async shutdown(): Promise<void> {\n // Flush before shutdown\n await this.flush();\n\n // End any remaining spans\n for (const [spanId, { otelSpan }] of this.otelSpanMap.entries()) {\n this.logger.warn(`[OtelBridge] Force-ending span that was not properly closed [id=${spanId}]`);\n otelSpan.end();\n }\n this.otelSpanMap.clear();\n this.logger.info('[OtelBridge] Shutdown complete');\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["otelTrace","otelLogs","otelContext"],"sources":["../src/bridge.ts"],"sourcesContent":["/**\n * OpenTelemetry Bridge for Mastra Observability\n *\n * This bridge enables bidirectional integration with OpenTelemetry infrastructure:\n * 1. Reads OTEL trace context from active spans (via AsyncLocalStorage)\n * 2. Creates real OTEL spans when Mastra spans are created\n * 3. Maintains span context for proper parent-child relationships\n * 4. Allows OTEL-instrumented code (DB, HTTP clients) in tools/workflows to have correct parents\n *\n * This creates complete distributed traces where Mastra spans are properly\n * nested within OTEL spans from auto-instrumentation, and any OTEL-instrumented\n * operations within Mastra spans maintain the correct hierarchy.\n */\n\nimport type {\n ObservabilityBridge,\n TracingEvent,\n LogEvent,\n CreateSpanOptions,\n SpanType,\n SpanIds,\n InitExporterOptions,\n SpanLink,\n} from '@mastra/core/observability';\nimport { TracingEventType } from '@mastra/core/observability';\nimport { BaseExporter, getExternalParentId } from '@mastra/observability';\nimport type { BaseExporterConfig } from '@mastra/observability';\nimport { SpanConverter, convertLog, getSpanKind } from '@mastra/otel-exporter';\nimport { trace as otelTrace, context as otelContext, isSpanContextValid, TraceFlags } from '@opentelemetry/api';\nimport type { Span as OtelSpan, Context as OtelContext, Link, TracerProvider, Tracer } from '@opentelemetry/api';\nimport { logs as otelLogs } from '@opentelemetry/api-logs';\nimport type { Logger as OtelLogger, LoggerProvider } from '@opentelemetry/api-logs';\n\nfunction linkKey(link: { traceId: string; spanId: string }): string {\n return `${link.traceId}:${link.spanId}`;\n}\n\n/** OpenTelemetry links for Mastra span links, skipping any whose IDs are not valid OpenTelemetry IDs. */\nfunction toOtelLinks(links: SpanLink[]): Link[] {\n return links\n .map(link => ({ traceId: link.traceId, spanId: link.spanId, traceFlags: TraceFlags.SAMPLED, isRemote: true }))\n .filter(context => isSpanContextValid(context))\n .map(context => ({ context }));\n}\n\nconst SETUP_DOCS_URL = 'https://mastra.ai/reference/observability/tracing/bridges/otel#setup-requirements';\n\nexport type OtelBridgeConfig = BaseExporterConfig & {\n tracerProvider?: TracerProvider;\n loggerProvider?: LoggerProvider;\n};\n\n/**\n * OpenTelemetry Bridge implementation\n *\n * Creates real OTEL spans when Mastra spans are created, maintaining proper\n * context propagation for nested instrumentation.\n *\n * @example\n * ```typescript\n * import { OtelBridge } from '@mastra/otel-bridge';\n * import { Mastra } from '@mastra/core';\n *\n * const mastra = new Mastra({\n * agents: { myAgent },\n * observability: {\n * configs: {\n * default: {\n * serviceName: 'my-service',\n * bridge: new OtelBridge(),\n * }\n * }\n * }\n * });\n * ```\n */\nexport class OtelBridge extends BaseExporter implements ObservabilityBridge {\n name = 'otel';\n private tracerProvider: TracerProvider;\n private loggerProvider: LoggerProvider;\n private otelTracer: Tracer;\n private otelLogger: OtelLogger;\n /** Live OTEL spans by span ID, with the keys of the links they were started with. */\n private otelSpanMap = new Map<string, { otelSpan: OtelSpan; otelContext: OtelContext; startLinks?: Set<string> }>();\n private spanConverter?: SpanConverter;\n private hasCustomTracerProvider: boolean;\n private hasCustomLoggerProvider: boolean;\n private warnedNoTracerProvider = false;\n\n constructor(config: OtelBridgeConfig = {}) {\n super(config);\n this.hasCustomTracerProvider = config.tracerProvider !== undefined;\n this.hasCustomLoggerProvider = config.loggerProvider !== undefined;\n this.tracerProvider = config.tracerProvider ?? otelTrace.getTracerProvider();\n this.otelTracer = this.tracerProvider.getTracer('@mastra/otel-bridge', '1.0.0');\n this.loggerProvider = config.loggerProvider ?? otelLogs.getLoggerProvider();\n this.otelLogger = this.loggerProvider.getLogger('@mastra/otel-bridge', '1.0.0');\n }\n\n /**\n * Handle Mastra tracing events\n *\n * Ships OTEL spans when Mastra spans end.\n * This maintains proper span hierarchy and allows OTEL-instrumented code within\n * Mastra spans to have correct parent-child relationships.\n * Note: OTEL spans are created when registerSpan is called when the span is first created.\n */\n protected async _exportTracingEvent(event: TracingEvent): Promise<void> {\n if (event.type === TracingEventType.SPAN_ENDED) {\n await this.handleSpanEnded(event);\n }\n }\n\n /**\n * Forward Mastra log events into the globally-registered OTEL LoggerProvider.\n *\n * If the user has not registered a LoggerProvider (e.g. via @opentelemetry/sdk-logs\n * or NodeSDK's logRecordProcessor option), the API returns a no-op logger and\n * emit() is a silent no-op — the bridge degrades gracefully.\n *\n * Trace correlation:\n * - If the log carries a spanId we have an OTEL span for, emit under that span's\n * stored context so the log nests beneath it in the trace.\n * - Else if the log carries traceId+spanId, attach a SpanContext built from those\n * IDs so backends still correlate by ID.\n * - Else emit under whatever context is currently active.\n */\n async onLogEvent(event: LogEvent): Promise<void> {\n if (this.isDisabled) return;\n\n try {\n const params = convertLog(event.log);\n\n const attributes = { ...params.attributes };\n if (params.traceId) attributes['mastra.traceId'] = params.traceId;\n if (params.spanId) attributes['mastra.spanId'] = params.spanId;\n\n const logContext = this.resolveLogContext(params.traceId, params.spanId);\n\n this.otelLogger.emit({\n timestamp: params.timestamp,\n severityNumber: params.severityNumber,\n severityText: params.severityText,\n body: params.body,\n attributes,\n context: logContext,\n });\n } catch (error) {\n this.logger.error('[OtelBridge] Failed to emit log:', error);\n }\n }\n\n /**\n * Pick the OTEL Context to emit a log under so trace correlation is correct.\n */\n private resolveLogContext(traceId?: string, spanId?: string): OtelContext {\n // 1. Prefer the stored OTEL context for the originating Mastra span.\n if (spanId) {\n const entry = this.otelSpanMap.get(spanId);\n if (entry) return entry.otelContext;\n }\n\n // 2. Fall back to a context with a span context built from the raw IDs,\n // but only when both IDs form a valid W3C span context. Injecting\n // malformed IDs would surface as garbage trace links downstream.\n if (traceId && spanId) {\n const candidate = {\n traceId,\n spanId,\n traceFlags: TraceFlags.SAMPLED,\n isRemote: false,\n };\n if (isSpanContextValid(candidate)) {\n return otelTrace.setSpanContext(otelContext.active(), candidate);\n }\n }\n\n // 3. Fall through to whatever is currently active.\n return otelContext.active();\n }\n\n /**\n * Initialize with tracing configuration\n */\n init(options: InitExporterOptions) {\n this.spanConverter = new SpanConverter({\n packageName: '@mastra/otel-bridge',\n serviceName: options.config?.serviceName,\n format: 'GenAI_v1_38_0',\n });\n }\n\n /**\n * Create a span in the bridge's tracing system.\n * Called during Mastra span construction to get bridge-generated identifiers.\n *\n * @param options - Span creation options from Mastra\n * @returns Span identifiers (spanId, traceId, parentSpanId) from bridge, or undefined if creation fails\n */\n createSpan(options: CreateSpanOptions<SpanType>): SpanIds | undefined {\n try {\n // Determine parent context\n let parentOtelContext = otelContext.active();\n let usedPersistedParent = false;\n\n // Get external parent ID (walks up chain to find non-internal parent)\n const externalParentId = getExternalParentId(options);\n if (externalParentId) {\n // Look up external parent's OTEL span from map\n const parentEntry = this.otelSpanMap.get(externalParentId);\n if (parentEntry) {\n parentOtelContext = parentEntry.otelContext;\n }\n } else if (options.traceId && options.parentSpanId) {\n // Root spans restored from persisted state (e.g. workflow suspend/resume)\n // carry the original trace ID and parent span ID, but the parent OTEL span\n // is no longer alive — it ended when the run suspended, possibly in another\n // process. Parent under a remote span context built from the persisted IDs\n // so the span continues the original trace instead of joining whatever\n // context happens to be active. Skip malformed IDs; injecting them would\n // surface as garbage trace links downstream.\n const candidate = {\n traceId: options.traceId,\n spanId: options.parentSpanId,\n traceFlags: TraceFlags.SAMPLED,\n isRemote: true,\n };\n if (isSpanContextValid(candidate)) {\n parentOtelContext = otelTrace.setSpanContext(parentOtelContext, candidate);\n usedPersistedParent = true;\n }\n }\n\n // Create OTEL span with SpanKind (must be set at creation, immutable)\n const otelSpan = this.otelTracer.startSpan(\n options.name,\n {\n kind: getSpanKind(options.type),\n ...(options.startTime ? { startTime: options.startTime } : {}),\n ...(options.links?.length ? { links: toOtelLinks(options.links) } : {}),\n },\n parentOtelContext,\n );\n\n // Create context with this span active\n const spanContext = otelTrace.setSpan(parentOtelContext, otelSpan);\n\n // Get OTEL span identifiers\n const otelSpanContext = otelSpan.spanContext();\n\n // If no OTEL SDK is registered, the global tracer is a no-op. With no\n // parent it returns a span with an invalid (all-zero) span context; with\n // a parent in the context (e.g. an outer span from another, unregistered\n // provider) it returns a span wrapping the parent's span context. Either\n // way, returning those IDs would collide across every Mastra span and\n // break downstream exporters. Bail out so DefaultSpan falls through to\n // its own ID generator. A real tracer always mints a fresh span ID, even\n // for spans its sampler drops.\n const parentOtelSpanContext = otelTrace.getSpanContext(parentOtelContext);\n if (\n !isSpanContextValid(otelSpanContext) ||\n (parentOtelSpanContext !== undefined && otelSpanContext.spanId === parentOtelSpanContext.spanId)\n ) {\n this.warnNoTracerProvider();\n // End the span we just started so its lifecycle stays clean on\n // providers that do track non-recording spans.\n otelSpan.end();\n return undefined;\n }\n\n const spanId = otelSpanContext.spanId;\n const traceId = otelSpanContext.traceId;\n\n // Store for later retrieval (for executeWithSpanContext and event handling)\n this.otelSpanMap.set(spanId, {\n otelSpan,\n otelContext: spanContext,\n ...(options.links?.length ? { startLinks: new Set(options.links.map(linkKey)) } : {}),\n });\n\n // Get parentSpanId from parent context if available\n const parentSpan = otelTrace.getSpan(parentOtelContext);\n const parentSpanContext = parentSpan?.spanContext();\n const parentSpanId =\n parentSpanContext && isSpanContextValid(parentSpanContext) ? parentSpanContext.spanId : undefined;\n\n // Declare which kind of parent was used: a span this bridge created for\n // a Mastra span is part of the Mastra trace, while anything else in the\n // ambient OTEL context belongs to the external tracing system. A parent\n // restored from persisted state is a Mastra span in storage — it ended at\n // suspend (possibly in another process), so it is absent from the live map.\n const parentIsMastraSpan =\n parentSpanId !== undefined && (this.otelSpanMap.has(parentSpanId) || usedPersistedParent);\n\n this.logger.debug(\n `[OtelBridge.createSpan] Created span [spanId=${spanId}] [traceId=${traceId}] ` +\n `[parentSpanId=${parentSpanId}] [parentIsMastraSpan=${parentIsMastraSpan}] ` +\n `[type=${options.type}] [mapSize=${this.otelSpanMap.size}]`,\n );\n\n return {\n spanId,\n traceId,\n ...(parentIsMastraSpan ? { parentSpanId } : { externalParentSpanId: parentSpanId }),\n };\n } catch (error) {\n this.logger.error('[OtelBridge] Failed to create span:', error);\n return undefined;\n }\n }\n\n /**\n * Warn once per bridge that no tracer provider is producing spans. Checked\n * lazily on span creation rather than at startup, because the OTEL SDK is\n * often registered after the bridge is constructed.\n */\n private warnNoTracerProvider(): void {\n if (this.warnedNoTracerProvider) return;\n this.warnedNoTracerProvider = true;\n\n const cause = this.hasCustomTracerProvider\n ? 'The tracerProvider passed to OtelBridge returned a no-op span'\n : 'No OpenTelemetry tracer provider is registered globally';\n\n this.logger.warn(\n `[OtelBridge] ${cause}, so Mastra spans will not be exported through OpenTelemetry. ` +\n 'Register a tracer provider before running agents or workflows (for example, call `sdk.start()` on ' +\n '`NodeSDK` from `@opentelemetry/sdk-node`, or `trace.setGlobalTracerProvider(provider)`), ' +\n 'or pass it directly with `new OtelBridge({ tracerProvider })`. ' +\n `See ${SETUP_DOCS_URL}`,\n );\n }\n\n /**\n * Handle SPAN_ENDED event\n *\n * Retrieves the OTEL span created at SPAN_STARTED, sets all final attributes,\n * events, and status, then ends the span. Cleans up the span map entry.\n */\n private async handleSpanEnded(event: TracingEvent): Promise<void> {\n try {\n const mastraSpan = event.exportedSpan;\n const entry = this.otelSpanMap.get(mastraSpan.id);\n\n if (!entry) {\n // Expected when no tracer provider is registered; warnNoTracerProvider()\n // already reported that once, so don't repeat it for every span.\n this.logger.debug(`[OtelBridge] No OTEL span found for Mastra span [id=${mastraSpan.id}].`);\n return;\n }\n\n // Remove from map immediately to prevent memory leak\n this.otelSpanMap.delete(mastraSpan.id);\n\n if (!this.spanConverter) {\n return;\n }\n\n const { otelSpan } = entry;\n\n this.logger.debug(`[OtelBridge] Ending OTEL span [mastraId=${mastraSpan.id}] [name=${mastraSpan.name}]`);\n\n // Use SpanConverter to get consistent span formatting with otel-exporter\n const readableSpan = await this.spanConverter!.convertSpan(mastraSpan);\n\n // Update span name to match the converter's formatting\n otelSpan.updateName(readableSpan.name);\n\n // Set all attributes from the converter (includes OTEL semantic conventions)\n for (const [key, value] of Object.entries(readableSpan.attributes)) {\n if (value !== undefined && value !== null && typeof value !== 'object') {\n otelSpan.setAttribute(key, value);\n }\n }\n\n // Set status from the converter\n otelSpan.setStatus(readableSpan.status);\n\n // Links set after the span started, such as an MCP tool call's link to the\n // server span that handled it\n const lateLinks = readableSpan.links.filter(link => !entry.startLinks?.has(linkKey(link.context)));\n if (lateLinks.length > 0 && typeof otelSpan.addLinks === 'function') {\n otelSpan.addLinks(lateLinks);\n }\n\n // Add exception events if present\n for (const event of readableSpan.events) {\n if (event.name === 'exception' && event.attributes) {\n const error = new Error(event.attributes['exception.message'] as string);\n otelSpan.recordException(error);\n }\n }\n\n // End the span with the actual end time\n otelSpan.end(mastraSpan.endTime);\n\n this.logger.debug(\n `[OtelBridge] Completed OTEL span [mastraId=${mastraSpan.id}] [traceId=${otelSpan.spanContext().traceId}]`,\n );\n } catch (error) {\n this.logger.error('[OtelBridge] Failed to handle SPAN_ENDED:', error);\n }\n }\n\n /**\n * Release the OTEL span held for a Mastra span that ended without being exported.\n *\n * The OTEL span is deliberately not ended: ending it would hand it to the OTEL\n * span processors and export a span the user's filtering just removed. Dropping\n * the reference is enough, since span processors only queue spans on end.\n */\n releaseSpan(spanId: string): void {\n if (this.otelSpanMap.delete(spanId)) {\n this.logger.debug(\n `[OtelBridge.releaseSpan] Released unexported span [spanId=${spanId}] [mapSize=${this.otelSpanMap.size}]`,\n );\n }\n }\n\n /**\n * Execute a function (sync or async) within the OTEL context of a Mastra span.\n * Retrieves the stored OTEL context for the span and executes the function within it.\n *\n * This is the core implementation used by both executeInContext and executeInContextSync.\n *\n * @param spanId - The ID of the Mastra span to use as context\n * @param fn - The function to execute within the span context\n * @returns The result of the function execution\n */\n private executeWithSpanContext<T>(spanId: string, fn: () => T): T {\n const entry = this.otelSpanMap.get(spanId);\n\n this.logger.debug(\n `[OtelBridge.executeWithSpanContext] spanId=${spanId}, ` +\n `inMap=${!!entry}, ` +\n `storedOtelSpan=${entry?.otelSpan.spanContext().spanId || 'none'}`,\n );\n\n const spanContext = entry?.otelContext;\n if (spanContext) {\n return otelContext.with(spanContext, fn);\n }\n return fn();\n }\n\n /**\n * Execute an async function within the OTEL context of a Mastra span.\n *\n * @param spanId - The ID of the Mastra span to use as context\n * @param fn - The async function to execute within the span context\n * @returns The result of the function execution\n */\n executeInContext<T>(spanId: string, fn: () => Promise<T>): Promise<T> {\n return this.executeWithSpanContext(spanId, fn);\n }\n\n /**\n * Execute a synchronous function within the OTEL context of a Mastra span.\n *\n * @param spanId - The ID of the Mastra span to use as context\n * @param fn - The synchronous function to execute within the span context\n * @returns The result of the function execution\n */\n executeInContextSync<T>(spanId: string, fn: () => T): T {\n return this.executeWithSpanContext(spanId, fn);\n }\n\n /**\n * Force flush any buffered spans without shutting down the bridge.\n *\n * Attempts to flush the underlying OTEL tracer provider if it supports\n * the forceFlush operation. This is useful in serverless environments\n * where you need to ensure all spans are exported before the runtime\n * instance is terminated.\n */\n async flush(): Promise<void> {\n // Re-resolve the global providers on every flush: the OTEL SDK is often\n // registered after the bridge is constructed, and `otelLogs.getLoggerProvider()`\n // only returns the real provider once one has been registered.\n const tracerProvider = this.hasCustomTracerProvider ? this.tracerProvider : otelTrace.getTracerProvider();\n const loggerProvider = this.hasCustomLoggerProvider ? this.loggerProvider : otelLogs.getLoggerProvider();\n await this.flushProvider(tracerProvider, 'tracer');\n await this.flushProvider(loggerProvider, 'logger');\n }\n\n private async flushProvider(provider: unknown, label: 'tracer' | 'logger'): Promise<void> {\n try {\n // `trace.getTracerProvider()` returns the API's ProxyTracerProvider once a\n // global provider is registered (e.g. by `NodeSDK.start()`). The proxy has\n // no forceFlush of its own; the SDK provider behind it does.\n if (\n provider &&\n typeof provider === 'object' &&\n 'getDelegate' in provider &&\n typeof (provider as { getDelegate: unknown }).getDelegate === 'function'\n ) {\n provider = (provider as { getDelegate: () => unknown }).getDelegate();\n }\n if (\n provider &&\n typeof provider === 'object' &&\n 'forceFlush' in provider &&\n typeof (provider as { forceFlush: unknown }).forceFlush === 'function'\n ) {\n await (provider as { forceFlush: () => Promise<void> }).forceFlush();\n this.logger.debug(`[OtelBridge] Flushed ${label} provider`);\n } else {\n this.logger.debug(\n `[OtelBridge] ${label === 'tracer' ? 'Tracer' : 'Logger'} provider does not support forceFlush`,\n );\n }\n } catch (error) {\n this.logger.error(`[OtelBridge] Failed to flush ${label} provider:`, error);\n }\n }\n\n /**\n * Shutdown the bridge and clean up resources\n */\n async shutdown(): Promise<void> {\n // End any remaining spans first so the flush below exports them too\n for (const [spanId, { otelSpan }] of this.otelSpanMap.entries()) {\n this.logger.warn(`[OtelBridge] Force-ending span that was not properly closed [id=${spanId}]`);\n otelSpan.end();\n }\n this.otelSpanMap.clear();\n\n await this.flush();\n this.logger.info('[OtelBridge] Shutdown complete');\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/otel-bridge",
3
- "version": "1.5.16-alpha.1",
3
+ "version": "1.5.16-alpha.2",
4
4
  "description": "OpenTelemetry observability bridge for Mastra",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -25,8 +25,8 @@
25
25
  "dependencies": {
26
26
  "@opentelemetry/api": "^1.9.1",
27
27
  "@opentelemetry/api-logs": "^0.221.0",
28
- "@mastra/observability": "1.20.0-alpha.0",
29
- "@mastra/otel-exporter": "1.4.7-alpha.1"
28
+ "@mastra/observability": "1.20.0-alpha.1",
29
+ "@mastra/otel-exporter": "1.4.7-alpha.2"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@opentelemetry/context-async-hooks": "^2.10.0",
@@ -39,7 +39,7 @@
39
39
  "@internal/ai-sdk-v5": "0.0.89",
40
40
  "@internal/types-builder": "0.0.117",
41
41
  "@internal/lint": "0.0.142",
42
- "@mastra/core": "1.77.0-alpha.3"
42
+ "@mastra/core": "1.77.0-alpha.4"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "@mastra/core": ">=1.16.0-0 <2.0.0-0",