@mastra/otel-bridge 1.0.23-alpha.2 → 1.1.0-alpha.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # @mastra/otel-bridge
2
2
 
3
+ ## 1.1.0-alpha.3
4
+
5
+ ### Minor Changes
6
+
7
+ - Added log forwarding to `@mastra/otel-bridge`. The bridge now also subscribes to Mastra log events and forwards them to the globally-registered OpenTelemetry `LoggerProvider`, alongside the spans it already creates. ([#13529](https://github.com/mastra-ai/mastra/pull/13529))
8
+
9
+ Logs that originate inside a Mastra span are emitted under that span's OTEL context, so backends like Datadog, Grafana, and Honeycomb correlate them with the surrounding trace automatically. Logs without trace context fall through to the currently active OTEL context.
10
+
11
+ To wire up logs alongside traces, register a `logRecordProcessor` on `NodeSDK`:
12
+
13
+ ```ts
14
+ import { NodeSDK } from '@opentelemetry/sdk-node';
15
+ import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
16
+ import { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
17
+
18
+ const sdk = new NodeSDK({
19
+ // ...trace config as usual
20
+ logRecordProcessor: new BatchLogRecordProcessor(new OTLPLogExporter()),
21
+ });
22
+ ```
23
+
24
+ If no `LoggerProvider` is registered, log emission is a silent no-op — traces continue to work as configured.
25
+
26
+ ### Patch Changes
27
+
28
+ - Updated dependencies [[`6a569eb`](https://github.com/mastra-ai/mastra/commit/6a569eb89b006ba4714eeb92019c652ffa30e4e3), [`5688881`](https://github.com/mastra-ai/mastra/commit/5688881669c7ed157f31ac77f6fc5f8d95ceea32)]:
29
+ - @mastra/otel-exporter@1.1.0-alpha.3
30
+ - @mastra/core@1.33.0-alpha.9
31
+
3
32
  ## 1.0.23-alpha.2
4
33
 
5
34
  ### Patch Changes
package/README.md CHANGED
@@ -20,6 +20,7 @@ Enables bidirectional integration between Mastra and OpenTelemetry infrastructur
20
20
  - Maintains proper parent-child relationships in distributed traces
21
21
  - Allows OTEL-instrumented code (DB calls, HTTP clients) within Mastra operations to nest correctly
22
22
  - Exports spans with OTEL semantic conventions for GenAI operations
23
+ - Forwards Mastra log events to the globally-registered OTEL `LoggerProvider`. Logs that originate inside a Mastra span are emitted under that span's OTEL context, so backends correlate logs to traces using the standard OTLP fields. If no `LoggerProvider` is registered, log emission is a silent no-op.
23
24
 
24
25
  ## Installation
25
26
 
@@ -155,17 +156,46 @@ When a Mastra span ends:
155
156
 
156
157
  The bridge provides `executeInContext()` and `executeInContextSync()` to run code within a Mastra span's OTEL context. This allows OTEL-instrumented code (DB clients, HTTP clients) to nest correctly under Mastra spans.
157
158
 
159
+ ### Log Forwarding
160
+
161
+ When a `LoggerProvider` is registered globally (e.g. via `@opentelemetry/sdk-logs`, or via `NodeSDK`'s `logRecordProcessor` option), the bridge forwards every Mastra log event to it as an OTEL `LogRecord`. Trace correlation is automatic:
162
+
163
+ 1. If the log carries a `spanId` the bridge created an OTEL span for, the log is emitted under that span's stored OTEL context — so it nests beneath the Mastra span in distributed traces.
164
+ 2. Otherwise, if the log carries `traceId` and `spanId`, those are attached to the emitted log record's `SpanContext` so backends can still correlate by ID.
165
+ 3. Otherwise, the log is emitted under whatever OTEL context is currently active.
166
+
167
+ Log severity, message body, structured `data`, and `metadata` are mapped to the OTEL `LogRecord` shape. `mastra.traceId` / `mastra.spanId` attributes are also attached for backends that key off attributes only.
168
+
169
+ To wire up logs alongside traces, pass `logRecordProcessor` to `NodeSDK`:
170
+
171
+ ```javascript
172
+ import { NodeSDK } from '@opentelemetry/sdk-node';
173
+ import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
174
+ import { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
175
+
176
+ const sdk = new NodeSDK({
177
+ // ...trace config as usual
178
+ logRecordProcessor: new BatchLogRecordProcessor(new OTLPLogExporter()),
179
+ });
180
+ ```
181
+
158
182
  ## Requirements
159
183
 
160
184
  - **Dependencies**:
161
185
  - `@mastra/core` >= 1.0.0
162
186
  - `@opentelemetry/api` >= 1.9.0
187
+ - `@opentelemetry/api-logs` >= 0.215.0
163
188
 
164
189
  **For Standard OTEL Setup:**
165
190
 
166
191
  - `@opentelemetry/sdk-node` >= 0.205.0
167
192
  - `@opentelemetry/auto-instrumentations-node` >= 0.64.1
168
193
 
194
+ **For Log Forwarding (optional):**
195
+
196
+ - `@opentelemetry/sdk-logs` >= 0.215.0
197
+ - An OTLP log exporter for your protocol (e.g. `@opentelemetry/exporter-logs-otlp-http`)
198
+
169
199
  ## License
170
200
 
171
201
  Apache 2.0
package/dist/bridge.d.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  * nested within OTEL spans from auto-instrumentation, and any OTEL-instrumented
12
12
  * operations within Mastra spans maintain the correct hierarchy.
13
13
  */
14
- import type { ObservabilityBridge, TracingEvent, CreateSpanOptions, SpanType, SpanIds, InitExporterOptions } from '@mastra/core/observability';
14
+ import type { ObservabilityBridge, TracingEvent, LogEvent, CreateSpanOptions, SpanType, SpanIds, InitExporterOptions } from '@mastra/core/observability';
15
15
  import { BaseExporter } from '@mastra/observability';
16
16
  /**
17
17
  * Configuration for the OtelBridge
@@ -44,6 +44,7 @@ export type OtelBridgeConfig = {};
44
44
  export declare class OtelBridge extends BaseExporter implements ObservabilityBridge {
45
45
  name: string;
46
46
  private otelTracer;
47
+ private otelLogger;
47
48
  private otelSpanMap;
48
49
  private spanConverter?;
49
50
  constructor(config?: OtelBridgeConfig);
@@ -56,6 +57,25 @@ export declare class OtelBridge extends BaseExporter implements ObservabilityBri
56
57
  * Note: OTEL spans are created when registerSpan is called when the span is first created.
57
58
  */
58
59
  protected _exportTracingEvent(event: TracingEvent): Promise<void>;
60
+ /**
61
+ * Forward Mastra log events into the globally-registered OTEL LoggerProvider.
62
+ *
63
+ * If the user has not registered a LoggerProvider (e.g. via @opentelemetry/sdk-logs
64
+ * or NodeSDK's logRecordProcessor option), the API returns a no-op logger and
65
+ * emit() is a silent no-op — the bridge degrades gracefully.
66
+ *
67
+ * Trace correlation:
68
+ * - If the log carries a spanId we have an OTEL span for, emit under that span's
69
+ * stored context so the log nests beneath it in the trace.
70
+ * - Else if the log carries traceId+spanId, attach a SpanContext built from those
71
+ * IDs so backends still correlate by ID.
72
+ * - Else emit under whatever context is currently active.
73
+ */
74
+ onLogEvent(event: LogEvent): Promise<void>;
75
+ /**
76
+ * Pick the OTEL Context to emit a log under so trace correlation is correct.
77
+ */
78
+ private resolveLogContext;
59
79
  /**
60
80
  * Initialize with tracing configuration
61
81
  */
@@ -111,6 +131,7 @@ export declare class OtelBridge extends BaseExporter implements ObservabilityBri
111
131
  * instance is terminated.
112
132
  */
113
133
  flush(): Promise<void>;
134
+ private flushProvider;
114
135
  /**
115
136
  * Shutdown the bridge and clean up resources
116
137
  */
@@ -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,iBAAiB,EACjB,QAAQ,EACR,OAAO,EACP,mBAAmB,EACpB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,YAAY,EAAuB,MAAM,uBAAuB,CAAC;AAK1E;;GAEG;AAEH,MAAM,MAAM,gBAAgB,GAAG,EAE9B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,UAAW,SAAQ,YAAa,YAAW,mBAAmB;IACzE,IAAI,SAAU;IACd,OAAO,CAAC,UAAU,CAAuD;IACzE,OAAO,CAAC,WAAW,CAAuE;IAC1F,OAAO,CAAC,aAAa,CAAC,CAAgB;gBAE1B,MAAM,GAAE,gBAAqB;IAIzC;;;;;;;OAOG;cACa,mBAAmB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAMvE;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE,mBAAmB;IAQjC;;;;;;OAMG;IACH,UAAU,CAAC,OAAO,EAAE,iBAAiB,CAAC,QAAQ,CAAC,GAAG,OAAO,GAAG,SAAS;IAiErE;;;;;OAKG;YACW,eAAe;IAwD7B;;;;;;;;;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;IAIrE;;;;;;OAMG;IACH,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC;IAIvD;;;;;;;OAOG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAe5B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAYhC"}
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,EACpB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,YAAY,EAAuB,MAAM,uBAAuB,CAAC;AAO1E;;GAEG;AAEH,MAAM,MAAM,gBAAgB,GAAG,EAE9B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,UAAW,SAAQ,YAAa,YAAW,mBAAmB;IACzE,IAAI,SAAU;IACd,OAAO,CAAC,UAAU,CAAuD;IACzE,OAAO,CAAC,UAAU,CAAkE;IACpF,OAAO,CAAC,WAAW,CAAuE;IAC1F,OAAO,CAAC,aAAa,CAAC,CAAgB;gBAE1B,MAAM,GAAE,gBAAqB;IAIzC;;;;;;;OAOG;cACa,mBAAmB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAMvE;;;;;;;;;;;;;OAaG;IACG,UAAU,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBhD;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA0BzB;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE,mBAAmB;IAQjC;;;;;;OAMG;IACH,UAAU,CAAC,OAAO,EAAE,iBAAiB,CAAC,QAAQ,CAAC,GAAG,OAAO,GAAG,SAAS;IAiErE;;;;;OAKG;YACW,eAAe;IAwD7B;;;;;;;;;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;IAIrE;;;;;;OAMG;IACH,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC;IAIvD;;;;;;;OAOG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAKd,aAAa;IAoB3B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAYhC"}
package/dist/index.cjs CHANGED
@@ -4,11 +4,13 @@ var observability$1 = require('@mastra/core/observability');
4
4
  var observability = require('@mastra/observability');
5
5
  var otelExporter = require('@mastra/otel-exporter');
6
6
  var api = require('@opentelemetry/api');
7
+ var apiLogs = require('@opentelemetry/api-logs');
7
8
 
8
9
  // src/bridge.ts
9
10
  var OtelBridge = class extends observability.BaseExporter {
10
11
  name = "otel";
11
12
  otelTracer = api.trace.getTracer("@mastra/otel-bridge", "1.0.0");
13
+ otelLogger = apiLogs.logs.getLogger("@mastra/otel-bridge", "1.0.0");
12
14
  otelSpanMap = /* @__PURE__ */ new Map();
13
15
  spanConverter;
14
16
  constructor(config = {}) {
@@ -27,6 +29,61 @@ var OtelBridge = class extends observability.BaseExporter {
27
29
  await this.handleSpanEnded(event);
28
30
  }
29
31
  }
32
+ /**
33
+ * Forward Mastra log events into the globally-registered OTEL LoggerProvider.
34
+ *
35
+ * If the user has not registered a LoggerProvider (e.g. via @opentelemetry/sdk-logs
36
+ * or NodeSDK's logRecordProcessor option), the API returns a no-op logger and
37
+ * emit() is a silent no-op — the bridge degrades gracefully.
38
+ *
39
+ * Trace correlation:
40
+ * - If the log carries a spanId we have an OTEL span for, emit under that span's
41
+ * stored context so the log nests beneath it in the trace.
42
+ * - Else if the log carries traceId+spanId, attach a SpanContext built from those
43
+ * IDs so backends still correlate by ID.
44
+ * - Else emit under whatever context is currently active.
45
+ */
46
+ async onLogEvent(event) {
47
+ if (this.isDisabled) return;
48
+ try {
49
+ const params = otelExporter.convertLog(event.log);
50
+ const attributes = { ...params.attributes };
51
+ if (params.traceId) attributes["mastra.traceId"] = params.traceId;
52
+ if (params.spanId) attributes["mastra.spanId"] = params.spanId;
53
+ const logContext = this.resolveLogContext(params.traceId, params.spanId);
54
+ this.otelLogger.emit({
55
+ timestamp: params.timestamp,
56
+ severityNumber: params.severityNumber,
57
+ severityText: params.severityText,
58
+ body: params.body,
59
+ attributes,
60
+ context: logContext
61
+ });
62
+ } catch (error) {
63
+ this.logger.error("[OtelBridge] Failed to emit log:", error);
64
+ }
65
+ }
66
+ /**
67
+ * Pick the OTEL Context to emit a log under so trace correlation is correct.
68
+ */
69
+ resolveLogContext(traceId, spanId) {
70
+ if (spanId) {
71
+ const entry = this.otelSpanMap.get(spanId);
72
+ if (entry) return entry.otelContext;
73
+ }
74
+ if (traceId && spanId) {
75
+ const candidate = {
76
+ traceId,
77
+ spanId,
78
+ traceFlags: api.TraceFlags.SAMPLED,
79
+ isRemote: false
80
+ };
81
+ if (api.isSpanContextValid(candidate)) {
82
+ return api.trace.setSpanContext(api.context.active(), candidate);
83
+ }
84
+ }
85
+ return api.context.active();
86
+ }
30
87
  /**
31
88
  * Initialize with tracing configuration
32
89
  */
@@ -174,16 +231,21 @@ var OtelBridge = class extends observability.BaseExporter {
174
231
  * instance is terminated.
175
232
  */
176
233
  async flush() {
234
+ await this.flushProvider(api.trace.getTracerProvider(), "tracer");
235
+ await this.flushProvider(apiLogs.logs.getLoggerProvider(), "logger");
236
+ }
237
+ async flushProvider(provider, label) {
177
238
  try {
178
- const provider = api.trace.getTracerProvider();
179
- if (provider && "forceFlush" in provider && typeof provider.forceFlush === "function") {
239
+ if (provider && typeof provider === "object" && "forceFlush" in provider && typeof provider.forceFlush === "function") {
180
240
  await provider.forceFlush();
181
- this.logger.debug("[OtelBridge] Flushed tracer provider");
241
+ this.logger.debug(`[OtelBridge] Flushed ${label} provider`);
182
242
  } else {
183
- this.logger.debug("[OtelBridge] Tracer provider does not support forceFlush");
243
+ this.logger.debug(
244
+ `[OtelBridge] ${label === "tracer" ? "Tracer" : "Logger"} provider does not support forceFlush`
245
+ );
184
246
  }
185
247
  } catch (error) {
186
- this.logger.error("[OtelBridge] Failed to flush tracer provider:", error);
248
+ this.logger.error(`[OtelBridge] Failed to flush ${label} provider:`, error);
187
249
  }
188
250
  }
189
251
  /**
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bridge.ts"],"names":["BaseExporter","otelTrace","TracingEventType","SpanConverter","otelContext","getExternalParentId","getSpanKind","isSpanContextValid","event"],"mappings":"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","file":"index.cjs","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 CreateSpanOptions,\n SpanType,\n SpanIds,\n InitExporterOptions,\n} from '@mastra/core/observability';\nimport { TracingEventType } from '@mastra/core/observability';\nimport { BaseExporter, getExternalParentId } from '@mastra/observability';\nimport { SpanConverter, getSpanKind } from '@mastra/otel-exporter';\nimport { trace as otelTrace, context as otelContext, isSpanContextValid } from '@opentelemetry/api';\nimport type { Span as OtelSpan, Context as OtelContext } from '@opentelemetry/api';\n\n/**\n * Configuration for the OtelBridge\n */\n\nexport type OtelBridgeConfig = {\n // Currently no configuration options - placeholder for future options\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 otelTracer = otelTrace.getTracer('@mastra/otel-bridge', '1.0.0');\n private otelSpanMap = new Map<string, { otelSpan: OtelSpan; otelContext: OtelContext }>();\n private spanConverter?: SpanConverter;\n\n constructor(config: OtelBridgeConfig = {}) {\n super(config);\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 * 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\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 }\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 },\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 returns a non-recording\n // span with an invalid span context (all-zero span/trace IDs). Returning\n // those IDs would collide across every Mastra span and break downstream\n // exporters. Bail out so DefaultSpan falls through to its own ID generator.\n if (!isSpanContextValid(otelSpanContext)) {\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, { otelSpan, otelContext: spanContext });\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 this.logger.debug(\n `[OtelBridge.createSpan] Created span [spanId=${spanId}] [traceId=${traceId}] ` +\n `[parentSpanId=${parentSpanId}] [type=${options.type}] [mapSize=${this.otelSpanMap.size}]`,\n );\n\n return { spanId, traceId, parentSpanId };\n } catch (error) {\n this.logger.error('[OtelBridge] Failed to create span:', error);\n return undefined;\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 this.logger.warn(`[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 // 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 * 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 try {\n const provider = otelTrace.getTracerProvider();\n // Check if the provider supports forceFlush (not all implementations do)\n if (provider && 'forceFlush' in provider && typeof provider.forceFlush === 'function') {\n await provider.forceFlush();\n this.logger.debug('[OtelBridge] Flushed tracer provider');\n } else {\n this.logger.debug('[OtelBridge] Tracer provider does not support forceFlush');\n }\n } catch (error) {\n this.logger.error('[OtelBridge] Failed to flush tracer 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"]}
1
+ {"version":3,"sources":["../src/bridge.ts"],"names":["BaseExporter","otelTrace","otelLogs","TracingEventType","convertLog","TraceFlags","isSpanContextValid","otelContext","SpanConverter","getExternalParentId","getSpanKind","event"],"mappings":"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","file":"index.cjs","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} from '@mastra/core/observability';\nimport { TracingEventType } from '@mastra/core/observability';\nimport { BaseExporter, getExternalParentId } 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 } from '@opentelemetry/api';\nimport { logs as otelLogs } from '@opentelemetry/api-logs';\nimport type { Logger as OtelLogger } from '@opentelemetry/api-logs';\n\n/**\n * Configuration for the OtelBridge\n */\n\nexport type OtelBridgeConfig = {\n // Currently no configuration options - placeholder for future options\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 otelTracer = otelTrace.getTracer('@mastra/otel-bridge', '1.0.0');\n private otelLogger: OtelLogger = otelLogs.getLogger('@mastra/otel-bridge', '1.0.0');\n private otelSpanMap = new Map<string, { otelSpan: OtelSpan; otelContext: OtelContext }>();\n private spanConverter?: SpanConverter;\n\n constructor(config: OtelBridgeConfig = {}) {\n super(config);\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\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 }\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 },\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 returns a non-recording\n // span with an invalid span context (all-zero span/trace IDs). Returning\n // those IDs would collide across every Mastra span and break downstream\n // exporters. Bail out so DefaultSpan falls through to its own ID generator.\n if (!isSpanContextValid(otelSpanContext)) {\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, { otelSpan, otelContext: spanContext });\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 this.logger.debug(\n `[OtelBridge.createSpan] Created span [spanId=${spanId}] [traceId=${traceId}] ` +\n `[parentSpanId=${parentSpanId}] [type=${options.type}] [mapSize=${this.otelSpanMap.size}]`,\n );\n\n return { spanId, traceId, parentSpanId };\n } catch (error) {\n this.logger.error('[OtelBridge] Failed to create span:', error);\n return undefined;\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 this.logger.warn(`[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 // 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 * 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(otelTrace.getTracerProvider(), 'tracer');\n await this.flushProvider(otelLogs.getLoggerProvider(), '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"]}
package/dist/index.js CHANGED
@@ -1,12 +1,14 @@
1
1
  import { TracingEventType } from '@mastra/core/observability';
2
2
  import { BaseExporter, getExternalParentId } from '@mastra/observability';
3
- import { SpanConverter, getSpanKind } from '@mastra/otel-exporter';
4
- import { trace, context, isSpanContextValid } from '@opentelemetry/api';
3
+ import { convertLog, SpanConverter, getSpanKind } from '@mastra/otel-exporter';
4
+ import { trace, isSpanContextValid, TraceFlags, context } from '@opentelemetry/api';
5
+ import { logs } from '@opentelemetry/api-logs';
5
6
 
6
7
  // src/bridge.ts
7
8
  var OtelBridge = class extends BaseExporter {
8
9
  name = "otel";
9
10
  otelTracer = trace.getTracer("@mastra/otel-bridge", "1.0.0");
11
+ otelLogger = logs.getLogger("@mastra/otel-bridge", "1.0.0");
10
12
  otelSpanMap = /* @__PURE__ */ new Map();
11
13
  spanConverter;
12
14
  constructor(config = {}) {
@@ -25,6 +27,61 @@ var OtelBridge = class extends BaseExporter {
25
27
  await this.handleSpanEnded(event);
26
28
  }
27
29
  }
30
+ /**
31
+ * Forward Mastra log events into the globally-registered OTEL LoggerProvider.
32
+ *
33
+ * If the user has not registered a LoggerProvider (e.g. via @opentelemetry/sdk-logs
34
+ * or NodeSDK's logRecordProcessor option), the API returns a no-op logger and
35
+ * emit() is a silent no-op — the bridge degrades gracefully.
36
+ *
37
+ * Trace correlation:
38
+ * - If the log carries a spanId we have an OTEL span for, emit under that span's
39
+ * stored context so the log nests beneath it in the trace.
40
+ * - Else if the log carries traceId+spanId, attach a SpanContext built from those
41
+ * IDs so backends still correlate by ID.
42
+ * - Else emit under whatever context is currently active.
43
+ */
44
+ async onLogEvent(event) {
45
+ if (this.isDisabled) return;
46
+ try {
47
+ const params = convertLog(event.log);
48
+ const attributes = { ...params.attributes };
49
+ if (params.traceId) attributes["mastra.traceId"] = params.traceId;
50
+ if (params.spanId) attributes["mastra.spanId"] = params.spanId;
51
+ const logContext = this.resolveLogContext(params.traceId, params.spanId);
52
+ this.otelLogger.emit({
53
+ timestamp: params.timestamp,
54
+ severityNumber: params.severityNumber,
55
+ severityText: params.severityText,
56
+ body: params.body,
57
+ attributes,
58
+ context: logContext
59
+ });
60
+ } catch (error) {
61
+ this.logger.error("[OtelBridge] Failed to emit log:", error);
62
+ }
63
+ }
64
+ /**
65
+ * Pick the OTEL Context to emit a log under so trace correlation is correct.
66
+ */
67
+ resolveLogContext(traceId, spanId) {
68
+ if (spanId) {
69
+ const entry = this.otelSpanMap.get(spanId);
70
+ if (entry) return entry.otelContext;
71
+ }
72
+ if (traceId && spanId) {
73
+ const candidate = {
74
+ traceId,
75
+ spanId,
76
+ traceFlags: TraceFlags.SAMPLED,
77
+ isRemote: false
78
+ };
79
+ if (isSpanContextValid(candidate)) {
80
+ return trace.setSpanContext(context.active(), candidate);
81
+ }
82
+ }
83
+ return context.active();
84
+ }
28
85
  /**
29
86
  * Initialize with tracing configuration
30
87
  */
@@ -172,16 +229,21 @@ var OtelBridge = class extends BaseExporter {
172
229
  * instance is terminated.
173
230
  */
174
231
  async flush() {
232
+ await this.flushProvider(trace.getTracerProvider(), "tracer");
233
+ await this.flushProvider(logs.getLoggerProvider(), "logger");
234
+ }
235
+ async flushProvider(provider, label) {
175
236
  try {
176
- const provider = trace.getTracerProvider();
177
- if (provider && "forceFlush" in provider && typeof provider.forceFlush === "function") {
237
+ if (provider && typeof provider === "object" && "forceFlush" in provider && typeof provider.forceFlush === "function") {
178
238
  await provider.forceFlush();
179
- this.logger.debug("[OtelBridge] Flushed tracer provider");
239
+ this.logger.debug(`[OtelBridge] Flushed ${label} provider`);
180
240
  } else {
181
- this.logger.debug("[OtelBridge] Tracer provider does not support forceFlush");
241
+ this.logger.debug(
242
+ `[OtelBridge] ${label === "tracer" ? "Tracer" : "Logger"} provider does not support forceFlush`
243
+ );
182
244
  }
183
245
  } catch (error) {
184
- this.logger.error("[OtelBridge] Failed to flush tracer provider:", error);
246
+ this.logger.error(`[OtelBridge] Failed to flush ${label} provider:`, error);
185
247
  }
186
248
  }
187
249
  /**
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bridge.ts"],"names":["otelTrace","otelContext","event"],"mappings":"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","file":"index.js","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 CreateSpanOptions,\n SpanType,\n SpanIds,\n InitExporterOptions,\n} from '@mastra/core/observability';\nimport { TracingEventType } from '@mastra/core/observability';\nimport { BaseExporter, getExternalParentId } from '@mastra/observability';\nimport { SpanConverter, getSpanKind } from '@mastra/otel-exporter';\nimport { trace as otelTrace, context as otelContext, isSpanContextValid } from '@opentelemetry/api';\nimport type { Span as OtelSpan, Context as OtelContext } from '@opentelemetry/api';\n\n/**\n * Configuration for the OtelBridge\n */\n\nexport type OtelBridgeConfig = {\n // Currently no configuration options - placeholder for future options\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 otelTracer = otelTrace.getTracer('@mastra/otel-bridge', '1.0.0');\n private otelSpanMap = new Map<string, { otelSpan: OtelSpan; otelContext: OtelContext }>();\n private spanConverter?: SpanConverter;\n\n constructor(config: OtelBridgeConfig = {}) {\n super(config);\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 * 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\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 }\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 },\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 returns a non-recording\n // span with an invalid span context (all-zero span/trace IDs). Returning\n // those IDs would collide across every Mastra span and break downstream\n // exporters. Bail out so DefaultSpan falls through to its own ID generator.\n if (!isSpanContextValid(otelSpanContext)) {\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, { otelSpan, otelContext: spanContext });\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 this.logger.debug(\n `[OtelBridge.createSpan] Created span [spanId=${spanId}] [traceId=${traceId}] ` +\n `[parentSpanId=${parentSpanId}] [type=${options.type}] [mapSize=${this.otelSpanMap.size}]`,\n );\n\n return { spanId, traceId, parentSpanId };\n } catch (error) {\n this.logger.error('[OtelBridge] Failed to create span:', error);\n return undefined;\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 this.logger.warn(`[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 // 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 * 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 try {\n const provider = otelTrace.getTracerProvider();\n // Check if the provider supports forceFlush (not all implementations do)\n if (provider && 'forceFlush' in provider && typeof provider.forceFlush === 'function') {\n await provider.forceFlush();\n this.logger.debug('[OtelBridge] Flushed tracer provider');\n } else {\n this.logger.debug('[OtelBridge] Tracer provider does not support forceFlush');\n }\n } catch (error) {\n this.logger.error('[OtelBridge] Failed to flush tracer 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"]}
1
+ {"version":3,"sources":["../src/bridge.ts"],"names":["otelTrace","otelLogs","otelContext","event"],"mappings":"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","file":"index.js","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} from '@mastra/core/observability';\nimport { TracingEventType } from '@mastra/core/observability';\nimport { BaseExporter, getExternalParentId } 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 } from '@opentelemetry/api';\nimport { logs as otelLogs } from '@opentelemetry/api-logs';\nimport type { Logger as OtelLogger } from '@opentelemetry/api-logs';\n\n/**\n * Configuration for the OtelBridge\n */\n\nexport type OtelBridgeConfig = {\n // Currently no configuration options - placeholder for future options\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 otelTracer = otelTrace.getTracer('@mastra/otel-bridge', '1.0.0');\n private otelLogger: OtelLogger = otelLogs.getLogger('@mastra/otel-bridge', '1.0.0');\n private otelSpanMap = new Map<string, { otelSpan: OtelSpan; otelContext: OtelContext }>();\n private spanConverter?: SpanConverter;\n\n constructor(config: OtelBridgeConfig = {}) {\n super(config);\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\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 }\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 },\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 returns a non-recording\n // span with an invalid span context (all-zero span/trace IDs). Returning\n // those IDs would collide across every Mastra span and break downstream\n // exporters. Bail out so DefaultSpan falls through to its own ID generator.\n if (!isSpanContextValid(otelSpanContext)) {\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, { otelSpan, otelContext: spanContext });\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 this.logger.debug(\n `[OtelBridge.createSpan] Created span [spanId=${spanId}] [traceId=${traceId}] ` +\n `[parentSpanId=${parentSpanId}] [type=${options.type}] [mapSize=${this.otelSpanMap.size}]`,\n );\n\n return { spanId, traceId, parentSpanId };\n } catch (error) {\n this.logger.error('[OtelBridge] Failed to create span:', error);\n return undefined;\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 this.logger.warn(`[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 // 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 * 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(otelTrace.getTracerProvider(), 'tracer');\n await this.flushProvider(otelLogs.getLoggerProvider(), '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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/otel-bridge",
3
- "version": "1.0.23-alpha.2",
3
+ "version": "1.1.0-alpha.3",
4
4
  "description": "OpenTelemetry observability bridge for Mastra",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -25,18 +25,20 @@
25
25
  "license": "Apache-2.0",
26
26
  "dependencies": {
27
27
  "@opentelemetry/api": "^1.9.1",
28
- "@mastra/otel-exporter": "1.0.23-alpha.2",
29
- "@mastra/observability": "1.12.0-alpha.2"
28
+ "@opentelemetry/api-logs": "^0.215.0",
29
+ "@mastra/observability": "1.12.0-alpha.2",
30
+ "@mastra/otel-exporter": "1.1.0-alpha.3"
30
31
  },
31
32
  "devDependencies": {
33
+ "@opentelemetry/sdk-logs": "^0.215.0",
32
34
  "@types/node": "22.19.15",
33
35
  "eslint": "^10.2.1",
34
36
  "tsup": "^8.5.1",
35
37
  "typescript": "^6.0.3",
36
38
  "vitest": "4.1.5",
37
- "@mastra/core": "1.33.0-alpha.8",
39
+ "@internal/types-builder": "0.0.67",
38
40
  "@internal/lint": "0.0.92",
39
- "@internal/types-builder": "0.0.67"
41
+ "@mastra/core": "1.33.0-alpha.9"
40
42
  },
41
43
  "peerDependencies": {
42
44
  "@mastra/core": ">=1.16.0-0 <2.0.0-0",