@mastra/otel-exporter 1.3.5 → 1.3.6

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/index.cjs CHANGED
@@ -1,1193 +1,1045 @@
1
- 'use strict';
2
-
3
- var observability = require('@mastra/core/observability');
4
- var observability$1 = require('@mastra/observability');
5
- var api = require('@opentelemetry/api');
6
- var core = require('@opentelemetry/core');
7
- var resources = require('@opentelemetry/resources');
8
- var sdkLogs = require('@opentelemetry/sdk-logs');
9
- var sdkTraceBase = require('@opentelemetry/sdk-trace-base');
10
- var semanticConventions = require('@opentelemetry/semantic-conventions');
11
- var apiLogs = require('@opentelemetry/api-logs');
12
- var fs = require('fs');
13
- var url = require('url');
14
- var incubating = require('@opentelemetry/semantic-conventions/incubating');
15
-
16
- // src/tracing.ts
17
-
18
- // src/loadExporter.ts
19
- var EXPORTER_SPECS = {
20
- traces: {
21
- "http/json": { pkg: "@opentelemetry/exporter-trace-otlp-http", exportName: "OTLPTraceExporter" },
22
- "http/protobuf": { pkg: "@opentelemetry/exporter-trace-otlp-proto", exportName: "OTLPTraceExporter" },
23
- grpc: {
24
- pkg: "@opentelemetry/exporter-trace-otlp-grpc",
25
- exportName: "OTLPTraceExporter",
26
- extras: ["@grpc/grpc-js"]
27
- },
28
- zipkin: { pkg: "@opentelemetry/exporter-zipkin", exportName: "ZipkinExporter" }
29
- },
30
- logs: {
31
- "http/json": { pkg: "@opentelemetry/exporter-logs-otlp-http", exportName: "OTLPLogExporter" },
32
- "http/protobuf": { pkg: "@opentelemetry/exporter-logs-otlp-proto", exportName: "OTLPLogExporter" },
33
- grpc: {
34
- pkg: "@opentelemetry/exporter-logs-otlp-grpc",
35
- exportName: "OTLPLogExporter",
36
- extras: ["@grpc/grpc-js"]
37
- }
38
- // zipkin: not supported
39
- }
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _mastra_core_observability = require("@mastra/core/observability");
3
+ let _mastra_observability = require("@mastra/observability");
4
+ let _opentelemetry_api = require("@opentelemetry/api");
5
+ let _opentelemetry_core = require("@opentelemetry/core");
6
+ let _opentelemetry_resources = require("@opentelemetry/resources");
7
+ let _opentelemetry_sdk_logs = require("@opentelemetry/sdk-logs");
8
+ let _opentelemetry_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
9
+ let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
10
+ let _opentelemetry_api_logs = require("@opentelemetry/api-logs");
11
+ let fs = require("fs");
12
+ let url = require("url");
13
+ let _opentelemetry_semantic_conventions_incubating = require("@opentelemetry/semantic-conventions/incubating");
14
+ //#region src/loadExporter.ts
15
+ const EXPORTER_SPECS = {
16
+ traces: {
17
+ "http/json": {
18
+ pkg: "@opentelemetry/exporter-trace-otlp-http",
19
+ exportName: "OTLPTraceExporter"
20
+ },
21
+ "http/protobuf": {
22
+ pkg: "@opentelemetry/exporter-trace-otlp-proto",
23
+ exportName: "OTLPTraceExporter"
24
+ },
25
+ grpc: {
26
+ pkg: "@opentelemetry/exporter-trace-otlp-grpc",
27
+ exportName: "OTLPTraceExporter",
28
+ extras: ["@grpc/grpc-js"]
29
+ },
30
+ zipkin: {
31
+ pkg: "@opentelemetry/exporter-zipkin",
32
+ exportName: "ZipkinExporter"
33
+ }
34
+ },
35
+ logs: {
36
+ "http/json": {
37
+ pkg: "@opentelemetry/exporter-logs-otlp-http",
38
+ exportName: "OTLPLogExporter"
39
+ },
40
+ "http/protobuf": {
41
+ pkg: "@opentelemetry/exporter-logs-otlp-proto",
42
+ exportName: "OTLPLogExporter"
43
+ },
44
+ grpc: {
45
+ pkg: "@opentelemetry/exporter-logs-otlp-grpc",
46
+ exportName: "OTLPLogExporter",
47
+ extras: ["@grpc/grpc-js"]
48
+ }
49
+ }
40
50
  };
41
- var cache = /* @__PURE__ */ new Map();
51
+ /** Cache keyed by "signal:protocol" */
52
+ const cache = /* @__PURE__ */ new Map();
53
+ /**
54
+ * Load a trace exporter for the given protocol.
55
+ * Backward-compatible with existing usage in tracing.ts.
56
+ */
42
57
  async function loadExporter(protocol, provider) {
43
- return loadSignalExporter("traces", protocol, provider);
58
+ return loadSignalExporter("traces", protocol, provider);
44
59
  }
60
+ /**
61
+ * Load a signal-specific exporter class for the given protocol.
62
+ * Returns the constructor, or null if the package isn't installed.
63
+ */
45
64
  async function loadSignalExporter(signal, protocol, provider) {
46
- const spec = EXPORTER_SPECS[signal]?.[protocol];
47
- if (!spec) {
48
- if (protocol === "zipkin") {
49
- console.warn(
50
- `[OtelExporter] Zipkin does not support OTLP ${signal}. ${capitalize(signal)} export will be disabled.`
51
- );
52
- }
53
- return null;
54
- }
55
- const cacheKey = `${signal}:${protocol}`;
56
- if (cache.has(cacheKey)) {
57
- return cache.get(cacheKey);
58
- }
59
- try {
60
- const mod = await import(spec.pkg);
61
- const ExporterClass = mod[spec.exportName];
62
- if (typeof ExporterClass !== "function") {
63
- console.error(
64
- `[OtelExporter] ${capitalize(signal)} ${protocol} exporter package "${spec.pkg}" did not expose a "${spec.exportName}" export. ${capitalize(signal)} export will be disabled.`
65
- );
66
- return null;
67
- }
68
- cache.set(cacheKey, ExporterClass);
69
- return ExporterClass;
70
- } catch {
71
- const providerInfo = provider ? ` (required for ${provider})` : "";
72
- const allPkgs = [spec.pkg, ...spec.extras ?? []].join(" ");
73
- console.error(
74
- `[OtelExporter] ${capitalize(signal)} ${protocol} exporter is not installed${providerInfo}.
75
- Install the required package(s):
76
- npm install ${allPkgs}`
77
- );
78
- return null;
79
- }
65
+ const spec = EXPORTER_SPECS[signal]?.[protocol];
66
+ if (!spec) {
67
+ if (protocol === "zipkin") console.warn(`[OtelExporter] Zipkin does not support OTLP ${signal}. ${capitalize(signal)} export will be disabled.`);
68
+ return null;
69
+ }
70
+ const cacheKey = `${signal}:${protocol}`;
71
+ if (cache.has(cacheKey)) return cache.get(cacheKey);
72
+ try {
73
+ const ExporterClass = (await import(spec.pkg))[spec.exportName];
74
+ if (typeof ExporterClass !== "function") {
75
+ console.error(`[OtelExporter] ${capitalize(signal)} ${protocol} exporter package "${spec.pkg}" did not expose a "${spec.exportName}" export. ${capitalize(signal)} export will be disabled.`);
76
+ return null;
77
+ }
78
+ cache.set(cacheKey, ExporterClass);
79
+ return ExporterClass;
80
+ } catch {
81
+ const providerInfo = provider ? ` (required for ${provider})` : "";
82
+ const allPkgs = [spec.pkg, ...spec.extras ?? []].join(" ");
83
+ console.error(`[OtelExporter] ${capitalize(signal)} ${protocol} exporter is not installed${providerInfo}.\n Install the required package(s):\n npm install ${allPkgs}`);
84
+ return null;
85
+ }
80
86
  }
81
87
  function capitalize(s) {
82
- return s.charAt(0).toUpperCase() + s.slice(1);
88
+ return s.charAt(0).toUpperCase() + s.slice(1);
83
89
  }
90
+ //#endregion
91
+ //#region src/log-converter.ts
92
+ /**
93
+ * Map Mastra LogLevel to OTEL SeverityNumber
94
+ */
84
95
  function mapSeverity(level) {
85
- switch (level) {
86
- case "debug":
87
- return apiLogs.SeverityNumber.DEBUG;
88
- case "info":
89
- return apiLogs.SeverityNumber.INFO;
90
- case "warn":
91
- return apiLogs.SeverityNumber.WARN;
92
- case "error":
93
- return apiLogs.SeverityNumber.ERROR;
94
- case "fatal":
95
- return apiLogs.SeverityNumber.FATAL;
96
- default:
97
- return apiLogs.SeverityNumber.UNSPECIFIED;
98
- }
96
+ switch (level) {
97
+ case "debug": return _opentelemetry_api_logs.SeverityNumber.DEBUG;
98
+ case "info": return _opentelemetry_api_logs.SeverityNumber.INFO;
99
+ case "warn": return _opentelemetry_api_logs.SeverityNumber.WARN;
100
+ case "error": return _opentelemetry_api_logs.SeverityNumber.ERROR;
101
+ case "fatal": return _opentelemetry_api_logs.SeverityNumber.FATAL;
102
+ default: return _opentelemetry_api_logs.SeverityNumber.UNSPECIFIED;
103
+ }
99
104
  }
100
- function dateToHrTime(date) {
101
- const ms = date.getTime();
102
- const seconds = Math.floor(ms / 1e3);
103
- const nanoseconds = ms % 1e3 * 1e6;
104
- return [seconds, nanoseconds];
105
+ /**
106
+ * Convert a Date to OTEL HrTime [seconds, nanoseconds]
107
+ */
108
+ function dateToHrTime$1(date) {
109
+ const ms = date.getTime();
110
+ return [Math.floor(ms / 1e3), ms % 1e3 * 1e6];
105
111
  }
112
+ /**
113
+ * Stringify a value for use as an OTEL log attribute. Falls back to a
114
+ * placeholder rather than throwing if the value contains a circular
115
+ * reference, throws from a custom toJSON, or otherwise cannot be serialized.
116
+ */
106
117
  function safeStringify(value) {
107
- try {
108
- return JSON.stringify(value);
109
- } catch {
110
- return "[unserializable]";
111
- }
118
+ try {
119
+ return JSON.stringify(value);
120
+ } catch {
121
+ return "[unserializable]";
122
+ }
112
123
  }
124
+ /**
125
+ * Build OTEL log attributes from ExportedLog fields.
126
+ * Includes trace correlation and metadata.
127
+ */
113
128
  function buildLogAttributes(log) {
114
- const attributes = {};
115
- if (log.data) {
116
- for (const [key, value] of Object.entries(log.data)) {
117
- if (value === null || value === void 0) continue;
118
- attributes[`mastra.log.${key}`] = typeof value === "object" ? safeStringify(value) : value;
119
- }
120
- }
121
- if (log.metadata) {
122
- for (const [key, value] of Object.entries(log.metadata)) {
123
- if (value === null || value === void 0) continue;
124
- attributes[`mastra.metadata.${key}`] = typeof value === "object" ? safeStringify(value) : value;
125
- }
126
- }
127
- if (log.tags?.length) {
128
- attributes["mastra.tags"] = safeStringify(log.tags);
129
- }
130
- return attributes;
129
+ const attributes = {};
130
+ if (log.data) for (const [key, value] of Object.entries(log.data)) {
131
+ if (value === null || value === void 0) continue;
132
+ attributes[`mastra.log.${key}`] = typeof value === "object" ? safeStringify(value) : value;
133
+ }
134
+ if (log.metadata) for (const [key, value] of Object.entries(log.metadata)) {
135
+ if (value === null || value === void 0) continue;
136
+ attributes[`mastra.metadata.${key}`] = typeof value === "object" ? safeStringify(value) : value;
137
+ }
138
+ if (log.tags?.length) attributes["mastra.tags"] = safeStringify(log.tags);
139
+ return attributes;
131
140
  }
141
+ /**
142
+ * Convert an ExportedLog into parameters suitable for OTEL Logger.emit()
143
+ */
132
144
  function convertLog(log) {
133
- return {
134
- timestamp: dateToHrTime(log.timestamp),
135
- severityNumber: mapSeverity(log.level),
136
- severityText: log.level.toUpperCase(),
137
- body: log.message,
138
- attributes: buildLogAttributes(log),
139
- traceId: log.traceId,
140
- spanId: log.spanId
141
- };
145
+ return {
146
+ timestamp: dateToHrTime$1(log.timestamp),
147
+ severityNumber: mapSeverity(log.level),
148
+ severityText: log.level.toUpperCase(),
149
+ body: log.message,
150
+ attributes: buildLogAttributes(log),
151
+ traceId: log.traceId,
152
+ spanId: log.spanId
153
+ };
142
154
  }
143
-
144
- // src/provider-configs.ts
155
+ //#endregion
156
+ //#region src/provider-configs.ts
145
157
  function resolveProviderConfig(config, debug) {
146
- const providerName = Object.keys(config)[0];
147
- if (debug) {
148
- console.info(`[OtelExporter:debug] Resolving provider config for: ${providerName}`);
149
- }
150
- let resolved;
151
- if ("dash0" in config) {
152
- resolved = resolveDash0Config(config.dash0);
153
- } else if ("signoz" in config) {
154
- resolved = resolveSignozConfig(config.signoz);
155
- } else if ("newrelic" in config) {
156
- resolved = resolveNewRelicConfig(config.newrelic);
157
- } else if ("traceloop" in config) {
158
- resolved = resolveTraceloopConfig(config.traceloop);
159
- } else if ("laminar" in config) {
160
- resolved = resolveLaminarConfig(config.laminar);
161
- } else if ("custom" in config) {
162
- resolved = resolveCustomConfig(config.custom);
163
- } else {
164
- const _exhaustive = config;
165
- return _exhaustive;
166
- }
167
- if (debug && resolved) {
168
- const maskedHeaders = {};
169
- for (const [key, value] of Object.entries(resolved.headers)) {
170
- maskedHeaders[key] = value ? "[REDACTED]" : "[EMPTY]";
171
- }
172
- console.info(`[OtelExporter:debug] Provider "${providerName}" resolved:`, {
173
- endpoint: resolved.endpoint,
174
- protocol: resolved.protocol,
175
- headers: maskedHeaders
176
- });
177
- } else if (debug && !resolved) {
178
- console.info(`[OtelExporter:debug] Provider "${providerName}" resolution returned null (disabled)`);
179
- }
180
- return resolved;
158
+ const providerName = Object.keys(config)[0];
159
+ if (debug) console.info(`[OtelExporter:debug] Resolving provider config for: ${providerName}`);
160
+ let resolved;
161
+ if ("dash0" in config) resolved = resolveDash0Config(config.dash0);
162
+ else if ("signoz" in config) resolved = resolveSignozConfig(config.signoz);
163
+ else if ("newrelic" in config) resolved = resolveNewRelicConfig(config.newrelic);
164
+ else if ("traceloop" in config) resolved = resolveTraceloopConfig(config.traceloop);
165
+ else if ("laminar" in config) resolved = resolveLaminarConfig(config.laminar);
166
+ else if ("custom" in config) resolved = resolveCustomConfig(config.custom);
167
+ else return config;
168
+ if (debug && resolved) {
169
+ const maskedHeaders = {};
170
+ for (const [key, value] of Object.entries(resolved.headers)) maskedHeaders[key] = value ? "[REDACTED]" : "[EMPTY]";
171
+ console.info(`[OtelExporter:debug] Provider "${providerName}" resolved:`, {
172
+ endpoint: resolved.endpoint,
173
+ protocol: resolved.protocol,
174
+ headers: maskedHeaders
175
+ });
176
+ } else if (debug && !resolved) console.info(`[OtelExporter:debug] Provider "${providerName}" resolution returned null (disabled)`);
177
+ return resolved;
181
178
  }
182
179
  function resolveDash0Config(config) {
183
- const apiKey = config.apiKey ?? process.env.DASH0_API_KEY;
184
- const configEndpoint = config.endpoint ?? process.env.DASH0_ENDPOINT;
185
- const dataset = config.dataset ?? process.env.DASH0_DATASET;
186
- if (!apiKey) {
187
- console.error(
188
- "[OtelExporter] Dash0 configuration requires apiKey. Set DASH0_API_KEY environment variable or pass it in config. Tracing will be disabled."
189
- );
190
- return null;
191
- }
192
- if (!configEndpoint) {
193
- console.error(
194
- "[OtelExporter] Dash0 configuration requires endpoint. Set DASH0_ENDPOINT environment variable or pass it in config. Tracing will be disabled."
195
- );
196
- return null;
197
- }
198
- let endpoint = configEndpoint;
199
- if (!endpoint.includes("/v1/traces")) {
200
- endpoint = `${endpoint}/v1/traces`;
201
- }
202
- const headers = {
203
- authorization: `Bearer ${apiKey}`
204
- // lowercase for gRPC metadata
205
- };
206
- if (dataset) {
207
- headers["dash0-dataset"] = dataset;
208
- }
209
- return {
210
- endpoint,
211
- headers,
212
- protocol: "grpc"
213
- // Use gRPC for Dash0
214
- };
180
+ const apiKey = config.apiKey ?? process.env.DASH0_API_KEY;
181
+ const configEndpoint = config.endpoint ?? process.env.DASH0_ENDPOINT;
182
+ const dataset = config.dataset ?? process.env.DASH0_DATASET;
183
+ if (!apiKey) {
184
+ console.error("[OtelExporter] Dash0 configuration requires apiKey. Set DASH0_API_KEY environment variable or pass it in config. Tracing will be disabled.");
185
+ return null;
186
+ }
187
+ if (!configEndpoint) {
188
+ console.error("[OtelExporter] Dash0 configuration requires endpoint. Set DASH0_ENDPOINT environment variable or pass it in config. Tracing will be disabled.");
189
+ return null;
190
+ }
191
+ let endpoint = configEndpoint;
192
+ if (!endpoint.includes("/v1/traces")) endpoint = `${endpoint}/v1/traces`;
193
+ const headers = { authorization: `Bearer ${apiKey}` };
194
+ if (dataset) headers["dash0-dataset"] = dataset;
195
+ return {
196
+ endpoint,
197
+ headers,
198
+ protocol: "grpc"
199
+ };
215
200
  }
216
201
  function resolveSignozConfig(config) {
217
- const apiKey = config.apiKey ?? process.env.SIGNOZ_API_KEY;
218
- const region = config.region ?? process.env.SIGNOZ_REGION;
219
- const configEndpoint = config.endpoint ?? process.env.SIGNOZ_ENDPOINT;
220
- if (!apiKey) {
221
- console.error(
222
- "[OtelExporter] SigNoz configuration requires apiKey. Set SIGNOZ_API_KEY environment variable or pass it in config. Tracing will be disabled."
223
- );
224
- return null;
225
- }
226
- const endpoint = configEndpoint || `https://ingest.${region || "us"}.signoz.cloud:443/v1/traces`;
227
- return {
228
- endpoint,
229
- headers: {
230
- "signoz-ingestion-key": apiKey
231
- },
232
- protocol: "http/protobuf"
233
- };
202
+ const apiKey = config.apiKey ?? process.env.SIGNOZ_API_KEY;
203
+ const region = config.region ?? process.env.SIGNOZ_REGION;
204
+ const configEndpoint = config.endpoint ?? process.env.SIGNOZ_ENDPOINT;
205
+ if (!apiKey) {
206
+ console.error("[OtelExporter] SigNoz configuration requires apiKey. Set SIGNOZ_API_KEY environment variable or pass it in config. Tracing will be disabled.");
207
+ return null;
208
+ }
209
+ return {
210
+ endpoint: configEndpoint || `https://ingest.${region || "us"}.signoz.cloud:443/v1/traces`,
211
+ headers: { "signoz-ingestion-key": apiKey },
212
+ protocol: "http/protobuf"
213
+ };
234
214
  }
235
215
  function resolveNewRelicConfig(config) {
236
- const apiKey = config.apiKey ?? process.env.NEW_RELIC_LICENSE_KEY;
237
- const configEndpoint = config.endpoint ?? process.env.NEW_RELIC_ENDPOINT;
238
- if (!apiKey) {
239
- console.error(
240
- "[OtelExporter] New Relic configuration requires apiKey (license key). Set NEW_RELIC_LICENSE_KEY environment variable or pass it in config. Tracing will be disabled."
241
- );
242
- return null;
243
- }
244
- const endpoint = configEndpoint || "https://otlp.nr-data.net:443/v1/traces";
245
- return {
246
- endpoint,
247
- headers: {
248
- "api-key": apiKey
249
- },
250
- protocol: "http/protobuf"
251
- };
216
+ const apiKey = config.apiKey ?? process.env.NEW_RELIC_LICENSE_KEY;
217
+ const configEndpoint = config.endpoint ?? process.env.NEW_RELIC_ENDPOINT;
218
+ if (!apiKey) {
219
+ console.error("[OtelExporter] New Relic configuration requires apiKey (license key). Set NEW_RELIC_LICENSE_KEY environment variable or pass it in config. Tracing will be disabled.");
220
+ return null;
221
+ }
222
+ return {
223
+ endpoint: configEndpoint || "https://otlp.nr-data.net:443/v1/traces",
224
+ headers: { "api-key": apiKey },
225
+ protocol: "http/protobuf"
226
+ };
252
227
  }
253
228
  function resolveTraceloopConfig(config) {
254
- const apiKey = config.apiKey ?? process.env.TRACELOOP_API_KEY;
255
- const destinationId = config.destinationId ?? process.env.TRACELOOP_DESTINATION_ID;
256
- const configEndpoint = config.endpoint ?? process.env.TRACELOOP_ENDPOINT;
257
- if (!apiKey) {
258
- console.error(
259
- "[OtelExporter] Traceloop configuration requires apiKey. Set TRACELOOP_API_KEY environment variable or pass it in config. Tracing will be disabled."
260
- );
261
- return null;
262
- }
263
- const endpoint = configEndpoint || "https://api.traceloop.com/v1/traces";
264
- const headers = {
265
- Authorization: `Bearer ${apiKey}`
266
- };
267
- if (destinationId) {
268
- headers["x-traceloop-destination-id"] = destinationId;
269
- }
270
- return {
271
- endpoint,
272
- headers,
273
- protocol: "http/json"
274
- };
229
+ const apiKey = config.apiKey ?? process.env.TRACELOOP_API_KEY;
230
+ const destinationId = config.destinationId ?? process.env.TRACELOOP_DESTINATION_ID;
231
+ const configEndpoint = config.endpoint ?? process.env.TRACELOOP_ENDPOINT;
232
+ if (!apiKey) {
233
+ console.error("[OtelExporter] Traceloop configuration requires apiKey. Set TRACELOOP_API_KEY environment variable or pass it in config. Tracing will be disabled.");
234
+ return null;
235
+ }
236
+ const endpoint = configEndpoint || "https://api.traceloop.com/v1/traces";
237
+ const headers = { Authorization: `Bearer ${apiKey}` };
238
+ if (destinationId) headers["x-traceloop-destination-id"] = destinationId;
239
+ return {
240
+ endpoint,
241
+ headers,
242
+ protocol: "http/json"
243
+ };
275
244
  }
276
245
  function resolveLaminarConfig(config) {
277
- const apiKey = config.apiKey ?? process.env.LMNR_PROJECT_API_KEY;
278
- const configEndpoint = config.endpoint ?? process.env.LAMINAR_ENDPOINT;
279
- if (!apiKey) {
280
- console.error(
281
- "[OtelExporter] Laminar configuration requires apiKey. Set LMNR_PROJECT_API_KEY environment variable or pass it in config. Tracing will be disabled."
282
- );
283
- return null;
284
- }
285
- const endpoint = configEndpoint || "https://api.lmnr.ai/v1/traces";
286
- const headers = {
287
- Authorization: `Bearer ${apiKey}`
288
- };
289
- return {
290
- endpoint,
291
- headers,
292
- protocol: "http/protobuf"
293
- // Use HTTP/protobuf instead of gRPC for better compatibility
294
- };
246
+ const apiKey = config.apiKey ?? process.env.LMNR_PROJECT_API_KEY;
247
+ const configEndpoint = config.endpoint ?? process.env.LAMINAR_ENDPOINT;
248
+ if (!apiKey) {
249
+ console.error("[OtelExporter] Laminar configuration requires apiKey. Set LMNR_PROJECT_API_KEY environment variable or pass it in config. Tracing will be disabled.");
250
+ return null;
251
+ }
252
+ return {
253
+ endpoint: configEndpoint || "https://api.lmnr.ai/v1/traces",
254
+ headers: { Authorization: `Bearer ${apiKey}` },
255
+ protocol: "http/protobuf"
256
+ };
295
257
  }
296
258
  function resolveCustomConfig(config) {
297
- if (!config.endpoint) {
298
- console.error("[OtelExporter] Custom configuration requires endpoint. Tracing will be disabled.");
299
- return null;
300
- }
301
- return {
302
- endpoint: config.endpoint,
303
- headers: config.headers || {},
304
- protocol: config.protocol || "http/json"
305
- };
259
+ if (!config.endpoint) {
260
+ console.error("[OtelExporter] Custom configuration requires endpoint. Tracing will be disabled.");
261
+ return null;
262
+ }
263
+ return {
264
+ endpoint: config.endpoint,
265
+ headers: config.headers || {},
266
+ protocol: config.protocol || "http/json"
267
+ };
306
268
  }
307
-
308
- // src/gen-ai-messages.ts
309
- var isMastraMessagePart = (p) => {
310
- return typeof p === "object" && p != null && "type" in p && (p.type === "text" || p.type === "tool-call" || p.type === "tool-result") && (p.type === "text" && "text" in p || p.type === "tool-call" && "toolCallId" in p && "toolName" in p && "input" in p || p.type === "tool-result" && "toolCallId" in p && "toolName" in p && "output" in p);
269
+ //#endregion
270
+ //#region src/gen-ai-messages.ts
271
+ const isMastraMessagePart = (p) => {
272
+ return typeof p === "object" && p != null && "type" in p && (p.type === "text" || p.type === "tool-call" || p.type === "tool-result") && (p.type === "text" && "text" in p || p.type === "tool-call" && "toolCallId" in p && "toolName" in p && "input" in p || p.type === "tool-result" && "toolCallId" in p && "toolName" in p && "output" in p);
311
273
  };
312
- var isMastraMessage = (m) => {
313
- return typeof m === "object" && m != null && "role" in m && "content" in m && (typeof m.content === "string" || Array.isArray(m.content) && m.content.every(isMastraMessagePart));
274
+ const isMastraMessage = (m) => {
275
+ return typeof m === "object" && m != null && "role" in m && "content" in m && (typeof m.content === "string" || Array.isArray(m.content) && m.content.every(isMastraMessagePart));
314
276
  };
315
- var convertMastraMessagesToGenAIMessages = (inputOutputString) => {
316
- try {
317
- const parsedIO = JSON.parse(inputOutputString);
318
- if (typeof parsedIO !== "object" || parsedIO == null || !("messages" in parsedIO) && !("text" in parsedIO)) {
319
- return inputOutputString;
320
- }
321
- if ("text" in parsedIO) {
322
- return JSON.stringify([
323
- {
324
- role: "assistant",
325
- parts: [{ type: "text", content: parsedIO.text }]
326
- }
327
- ]);
328
- }
329
- if (Array.isArray(parsedIO.messages)) {
330
- return JSON.stringify(
331
- parsedIO.messages.map((m) => {
332
- if (!isMastraMessage(m)) {
333
- return m;
334
- }
335
- const role = m.role;
336
- let parts = [];
337
- if (Array.isArray(m.content)) {
338
- parts = m.content.map((c) => {
339
- switch (c.type) {
340
- case "text":
341
- return {
342
- type: "text",
343
- content: c.text
344
- };
345
- case "tool-call":
346
- return {
347
- type: "tool_call",
348
- id: c.toolCallId,
349
- name: c.toolName,
350
- arguments: JSON.stringify(c.input)
351
- };
352
- case "tool-result":
353
- return {
354
- type: "tool_call_response",
355
- id: c.toolCallId,
356
- name: c.toolName,
357
- response: JSON.stringify(c.output.value)
358
- };
359
- default:
360
- return c;
361
- }
362
- });
363
- } else {
364
- parts = [
365
- {
366
- type: "text",
367
- content: m.content
368
- }
369
- ];
370
- }
371
- return {
372
- role,
373
- parts
374
- };
375
- })
376
- );
377
- }
378
- return inputOutputString;
379
- } catch {
380
- return inputOutputString;
381
- }
277
+ /**
278
+ * Convert an Input/Output string from a MastraSpan into a jsonified string that adheres to
279
+ * OpenTelemetry gen_ai.input.messages and gen_ai.output.messages schema.
280
+ * If parsing fails at any step, the original inputOutputString is returned unmodified.
281
+ *
282
+ * This conversion is best effort; It assumes a consistent shape for mastra messages, and converts
283
+ * into the gen_ai input and output schemas as of October 20th, 2025.
284
+ *
285
+ * @see https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/#gen-ai-input-messages
286
+ * @see https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-input-messages.json
287
+ * @see https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/#gen-ai-output-messages
288
+ * @see https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-output-messages.json
289
+ *
290
+ * @param inputOutputString a jsonified string that contains messages adhering to what appears to be
291
+ * Mastra's message shape.
292
+ * @returns a jsonified string that contains messages adhering to the OpenTelemetry gen_ai.input.messages and gen_ai.output.messages schema.
293
+ * If parsing fails at any step, the original inputOutputString is returned unmodified.
294
+ */
295
+ const convertMastraMessagesToGenAIMessages = (inputOutputString) => {
296
+ try {
297
+ const parsedIO = JSON.parse(inputOutputString);
298
+ if (typeof parsedIO !== "object" || parsedIO == null || !("messages" in parsedIO) && !("text" in parsedIO)) return inputOutputString;
299
+ if ("text" in parsedIO) return JSON.stringify([{
300
+ role: "assistant",
301
+ parts: [{
302
+ type: "text",
303
+ content: parsedIO.text
304
+ }]
305
+ }]);
306
+ if (Array.isArray(parsedIO.messages)) return JSON.stringify(parsedIO.messages.map((m) => {
307
+ if (!isMastraMessage(m)) return m;
308
+ const role = m.role;
309
+ let parts = [];
310
+ if (Array.isArray(m.content)) parts = m.content.map((c) => {
311
+ switch (c.type) {
312
+ case "text": return {
313
+ type: "text",
314
+ content: c.text
315
+ };
316
+ case "tool-call": return {
317
+ type: "tool_call",
318
+ id: c.toolCallId,
319
+ name: c.toolName,
320
+ arguments: JSON.stringify(c.input)
321
+ };
322
+ case "tool-result": return {
323
+ type: "tool_call_response",
324
+ id: c.toolCallId,
325
+ name: c.toolName,
326
+ response: JSON.stringify(c.output.value)
327
+ };
328
+ default: return c;
329
+ }
330
+ });
331
+ else parts = [{
332
+ type: "text",
333
+ content: m.content
334
+ }];
335
+ return {
336
+ role,
337
+ parts
338
+ };
339
+ }));
340
+ return inputOutputString;
341
+ } catch {
342
+ return inputOutputString;
343
+ }
382
344
  };
383
-
384
- // src/gen-ai-semantics.ts
345
+ //#endregion
346
+ //#region src/gen-ai-semantics.ts
347
+ /**
348
+ * Utilities for converting Mastra Spans to OTel Spans
349
+ * with Semantic conventions for generative AI systems
350
+ * @see https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/gen-ai/README.md
351
+ * @see https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/gen-ai/gen-ai-events.md
352
+ * @see https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/gen-ai/gen-ai-spans.md
353
+ * @see https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/gen-ai/gen-ai-agent-spans.md
354
+ * @see https://opentelemetry.io/docs/specs/semconv/gen-ai/non-normative/examples-llm-calls/
355
+ * @see https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/
356
+ */
357
+ /**
358
+ * Formats UsageStats to OTel GenAI semantic convention attributes.
359
+ */
385
360
  function formatUsageMetrics(usage) {
386
- if (!usage) return {};
387
- const metrics = {};
388
- if (usage.inputTokens !== void 0) {
389
- metrics[incubating.ATTR_GEN_AI_USAGE_INPUT_TOKENS] = usage.inputTokens;
390
- }
391
- if (usage.outputTokens !== void 0) {
392
- metrics[incubating.ATTR_GEN_AI_USAGE_OUTPUT_TOKENS] = usage.outputTokens;
393
- }
394
- if (usage.outputDetails?.reasoning !== void 0) {
395
- metrics["gen_ai.usage.reasoning_tokens"] = usage.outputDetails.reasoning;
396
- }
397
- if (usage.inputDetails?.cacheRead !== void 0) {
398
- metrics[incubating.ATTR_GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS] = usage.inputDetails.cacheRead;
399
- }
400
- if (usage.inputDetails?.cacheWrite !== void 0) {
401
- metrics[incubating.ATTR_GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS] = usage.inputDetails.cacheWrite;
402
- }
403
- if (usage.inputDetails?.audio !== void 0) {
404
- metrics["gen_ai.usage.audio_input_tokens"] = usage.inputDetails.audio;
405
- }
406
- if (usage.outputDetails?.audio !== void 0) {
407
- metrics["gen_ai.usage.audio_output_tokens"] = usage.outputDetails.audio;
408
- }
409
- return metrics;
361
+ if (!usage) return {};
362
+ const metrics = {};
363
+ if (usage.inputTokens !== void 0) metrics[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_USAGE_INPUT_TOKENS] = usage.inputTokens;
364
+ if (usage.outputTokens !== void 0) metrics[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_USAGE_OUTPUT_TOKENS] = usage.outputTokens;
365
+ if (usage.outputDetails?.reasoning !== void 0) metrics["gen_ai.usage.reasoning_tokens"] = usage.outputDetails.reasoning;
366
+ if (usage.inputDetails?.cacheRead !== void 0) metrics[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS] = usage.inputDetails.cacheRead;
367
+ if (usage.inputDetails?.cacheWrite !== void 0) metrics[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS] = usage.inputDetails.cacheWrite;
368
+ if (usage.inputDetails?.audio !== void 0) metrics["gen_ai.usage.audio_input_tokens"] = usage.inputDetails.audio;
369
+ if (usage.outputDetails?.audio !== void 0) metrics["gen_ai.usage.audio_output_tokens"] = usage.outputDetails.audio;
370
+ return metrics;
410
371
  }
411
372
  function addModelRequestAttributes(attributes, attrs) {
412
- if (attrs.model) {
413
- attributes[incubating.ATTR_GEN_AI_REQUEST_MODEL] = attrs.model;
414
- }
415
- if (attrs.provider) {
416
- attributes[incubating.ATTR_GEN_AI_PROVIDER_NAME] = normalizeProvider(attrs.provider);
417
- }
418
- Object.assign(attributes, formatUsageMetrics(attrs.usage));
373
+ if (attrs.model) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_REQUEST_MODEL] = attrs.model;
374
+ if (attrs.provider) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_PROVIDER_NAME] = normalizeProvider(attrs.provider);
375
+ Object.assign(attributes, formatUsageMetrics(attrs.usage));
419
376
  }
377
+ /**
378
+ * Get the operation name based on span type for gen_ai.operation.name
379
+ */
420
380
  function getOperationName(span) {
421
- switch (span.type) {
422
- case observability.SpanType.MODEL_GENERATION:
423
- return "chat";
424
- case observability.SpanType.RAG_EMBEDDING:
425
- return "embeddings";
426
- case observability.SpanType.TOOL_CALL:
427
- case observability.SpanType.MCP_TOOL_CALL:
428
- case observability.SpanType.PROVIDER_TOOL_CALL:
429
- return "execute_tool";
430
- case observability.SpanType.AGENT_RUN:
431
- return "invoke_agent";
432
- case observability.SpanType.WORKFLOW_RUN:
433
- return "invoke_workflow";
434
- default:
435
- return span.type.toLowerCase();
436
- }
381
+ switch (span.type) {
382
+ case _mastra_core_observability.SpanType.MODEL_GENERATION: return "chat";
383
+ case _mastra_core_observability.SpanType.RAG_EMBEDDING: return "embeddings";
384
+ case _mastra_core_observability.SpanType.TOOL_CALL:
385
+ case _mastra_core_observability.SpanType.MCP_TOOL_CALL:
386
+ case _mastra_core_observability.SpanType.PROVIDER_TOOL_CALL: return "execute_tool";
387
+ case _mastra_core_observability.SpanType.AGENT_RUN: return "invoke_agent";
388
+ case _mastra_core_observability.SpanType.WORKFLOW_RUN: return "invoke_workflow";
389
+ default: return span.type.toLowerCase();
390
+ }
437
391
  }
392
+ /**
393
+ * Keep only unicode letters, numbers, dot, underscore, space, dash.
394
+ */
438
395
  function sanitizeSpanName(name) {
439
- return name.replace(/[^\p{L}\p{N}._ -]/gu, "");
396
+ return name.replace(/[^\p{L}\p{N}._ -]/gu, "");
440
397
  }
441
398
  function getSpanIdentifier(span) {
442
- switch (span.type) {
443
- case observability.SpanType.MODEL_GENERATION: {
444
- const attrs = span.attributes;
445
- return attrs?.model;
446
- }
447
- case observability.SpanType.RAG_EMBEDDING: {
448
- const attrs = span.attributes;
449
- return attrs?.model;
450
- }
451
- default:
452
- return span.entityName ?? span.entityId;
453
- }
399
+ switch (span.type) {
400
+ case _mastra_core_observability.SpanType.MODEL_GENERATION: return span.attributes?.model;
401
+ case _mastra_core_observability.SpanType.RAG_EMBEDDING: return span.attributes?.model;
402
+ default: return span.entityName ?? span.entityId;
403
+ }
454
404
  }
405
+ /**
406
+ * Get an OTEL-compliant span name based on span type and attributes
407
+ */
455
408
  function getSpanName(span) {
456
- const identifier = getSpanIdentifier(span);
457
- if (identifier) {
458
- const operation = getOperationName(span);
459
- return `${operation} ${identifier}`;
460
- }
461
- return sanitizeSpanName(span.name);
409
+ const identifier = getSpanIdentifier(span);
410
+ if (identifier) return `${getOperationName(span)} ${identifier}`;
411
+ return sanitizeSpanName(span.name);
462
412
  }
413
+ /**
414
+ * Gets OpenTelemetry attributes from Mastra Span
415
+ * Following OTEL Semantic Conventions for GenAI
416
+ */
463
417
  function getAttributes(span) {
464
- const attributes = {};
465
- const spanType = span.type.toLowerCase();
466
- attributes[incubating.ATTR_GEN_AI_OPERATION_NAME] = getOperationName(span);
467
- attributes["mastra.span.type"] = span.type;
468
- if (span.input !== void 0) {
469
- const inputStr = typeof span.input === "string" ? span.input : JSON.stringify(span.input);
470
- if (span.type === observability.SpanType.MODEL_GENERATION) {
471
- attributes[incubating.ATTR_GEN_AI_INPUT_MESSAGES] = convertMastraMessagesToGenAIMessages(inputStr);
472
- } else if (span.type === observability.SpanType.TOOL_CALL || span.type === observability.SpanType.MCP_TOOL_CALL || span.type === observability.SpanType.PROVIDER_TOOL_CALL) {
473
- attributes["gen_ai.tool.call.arguments"] = inputStr;
474
- } else {
475
- attributes[`mastra.${spanType}.input`] = inputStr;
476
- }
477
- }
478
- if (span.output !== void 0) {
479
- const outputStr = typeof span.output === "string" ? span.output : JSON.stringify(span.output);
480
- if (span.type === observability.SpanType.MODEL_GENERATION) {
481
- attributes[incubating.ATTR_GEN_AI_OUTPUT_MESSAGES] = convertMastraMessagesToGenAIMessages(outputStr);
482
- } else if (span.type === observability.SpanType.TOOL_CALL || span.type === observability.SpanType.MCP_TOOL_CALL || span.type === observability.SpanType.PROVIDER_TOOL_CALL) {
483
- attributes["gen_ai.tool.call.result"] = outputStr;
484
- } else {
485
- attributes[`mastra.${spanType}.output`] = outputStr;
486
- }
487
- }
488
- if (span.type === observability.SpanType.MODEL_GENERATION && span.attributes) {
489
- const modelAttrs = span.attributes;
490
- addModelRequestAttributes(attributes, modelAttrs);
491
- if (span.entityId) {
492
- attributes[incubating.ATTR_GEN_AI_AGENT_ID] = span.entityId;
493
- }
494
- if (span.entityName) {
495
- attributes[incubating.ATTR_GEN_AI_AGENT_NAME] = span.entityName;
496
- }
497
- if (modelAttrs.parameters) {
498
- if (modelAttrs.parameters.temperature !== void 0) {
499
- attributes[incubating.ATTR_GEN_AI_REQUEST_TEMPERATURE] = modelAttrs.parameters.temperature;
500
- }
501
- if (modelAttrs.parameters.maxOutputTokens !== void 0) {
502
- attributes[incubating.ATTR_GEN_AI_REQUEST_MAX_TOKENS] = modelAttrs.parameters.maxOutputTokens;
503
- }
504
- if (modelAttrs.parameters.topP !== void 0) {
505
- attributes[incubating.ATTR_GEN_AI_REQUEST_TOP_P] = modelAttrs.parameters.topP;
506
- }
507
- if (modelAttrs.parameters.topK !== void 0) {
508
- attributes[incubating.ATTR_GEN_AI_REQUEST_TOP_K] = modelAttrs.parameters.topK;
509
- }
510
- if (modelAttrs.parameters.presencePenalty !== void 0) {
511
- attributes[incubating.ATTR_GEN_AI_REQUEST_PRESENCE_PENALTY] = modelAttrs.parameters.presencePenalty;
512
- }
513
- if (modelAttrs.parameters.frequencyPenalty !== void 0) {
514
- attributes[incubating.ATTR_GEN_AI_REQUEST_FREQUENCY_PENALTY] = modelAttrs.parameters.frequencyPenalty;
515
- }
516
- if (modelAttrs.parameters.stopSequences) {
517
- attributes[incubating.ATTR_GEN_AI_REQUEST_STOP_SEQUENCES] = JSON.stringify(modelAttrs.parameters.stopSequences);
518
- }
519
- if (modelAttrs.parameters.seed) {
520
- attributes[incubating.ATTR_GEN_AI_REQUEST_SEED] = modelAttrs.parameters.seed;
521
- }
522
- }
523
- if (modelAttrs.completionStartTime) {
524
- attributes["mastra.completion_start_time"] = modelAttrs.completionStartTime.toISOString();
525
- }
526
- if (modelAttrs.finishReason) {
527
- attributes[incubating.ATTR_GEN_AI_RESPONSE_FINISH_REASONS] = JSON.stringify([modelAttrs.finishReason]);
528
- }
529
- if (modelAttrs.responseModel) {
530
- attributes[incubating.ATTR_GEN_AI_RESPONSE_MODEL] = modelAttrs.responseModel;
531
- }
532
- if (modelAttrs.responseId) {
533
- attributes[incubating.ATTR_GEN_AI_RESPONSE_ID] = modelAttrs.responseId;
534
- }
535
- if (modelAttrs.serverAddress) {
536
- attributes[incubating.ATTR_SERVER_ADDRESS] = modelAttrs.serverAddress;
537
- }
538
- if (modelAttrs.serverPort !== void 0) {
539
- attributes[incubating.ATTR_SERVER_PORT] = modelAttrs.serverPort;
540
- }
541
- }
542
- if (span.type === observability.SpanType.RAG_EMBEDDING && span.attributes) {
543
- const embeddingAttrs = span.attributes;
544
- addModelRequestAttributes(attributes, embeddingAttrs);
545
- if (embeddingAttrs.mode) {
546
- attributes[`mastra.${spanType}.mode`] = embeddingAttrs.mode;
547
- }
548
- if (embeddingAttrs.dimensions !== void 0) {
549
- attributes["gen_ai.embeddings.dimension.count"] = embeddingAttrs.dimensions;
550
- attributes[`mastra.${spanType}.dimensions`] = embeddingAttrs.dimensions;
551
- }
552
- if (embeddingAttrs.inputCount !== void 0) {
553
- attributes[`mastra.${spanType}.input_count`] = embeddingAttrs.inputCount;
554
- }
555
- }
556
- if ((span.type === observability.SpanType.TOOL_CALL || span.type === observability.SpanType.MCP_TOOL_CALL || span.type === observability.SpanType.PROVIDER_TOOL_CALL) && span.attributes) {
557
- attributes[incubating.ATTR_GEN_AI_TOOL_NAME] = span.entityName ?? span.entityId;
558
- if (span.type === observability.SpanType.MCP_TOOL_CALL) {
559
- const mcpAttrs = span.attributes;
560
- if (mcpAttrs.mcpServer) {
561
- attributes[incubating.ATTR_SERVER_ADDRESS] = mcpAttrs.mcpServer;
562
- }
563
- } else {
564
- const toolAttrs = span.attributes;
565
- if (toolAttrs.toolDescription) {
566
- attributes[incubating.ATTR_GEN_AI_TOOL_DESCRIPTION] = toolAttrs.toolDescription;
567
- }
568
- if (toolAttrs.toolType) {
569
- attributes["gen_ai.tool.type"] = toolAttrs.toolType;
570
- }
571
- }
572
- }
573
- if (span.type === observability.SpanType.AGENT_RUN && span.attributes) {
574
- const agentAttrs = span.attributes;
575
- if (span.entityId) {
576
- attributes[incubating.ATTR_GEN_AI_AGENT_ID] = span.entityId;
577
- }
578
- if (span.entityName) {
579
- attributes[incubating.ATTR_GEN_AI_AGENT_NAME] = span.entityName;
580
- }
581
- if (agentAttrs.conversationId) {
582
- attributes[incubating.ATTR_GEN_AI_CONVERSATION_ID] = agentAttrs.conversationId;
583
- }
584
- if (agentAttrs.maxSteps) {
585
- attributes[`mastra.${spanType}.max_steps`] = agentAttrs.maxSteps;
586
- }
587
- if (agentAttrs.availableTools) {
588
- attributes[`gen_ai.tool.definitions`] = JSON.stringify(agentAttrs.availableTools);
589
- }
590
- attributes[incubating.ATTR_GEN_AI_SYSTEM_INSTRUCTIONS] = agentAttrs.instructions;
591
- }
592
- if (span.errorInfo) {
593
- attributes[incubating.ATTR_ERROR_TYPE] = span.errorInfo.id || "unknown";
594
- attributes[incubating.ATTR_ERROR_MESSAGE] = span.errorInfo.message;
595
- if (span.errorInfo.domain) {
596
- attributes["error.domain"] = span.errorInfo.domain;
597
- }
598
- if (span.errorInfo.category) {
599
- attributes["error.category"] = span.errorInfo.category;
600
- }
601
- }
602
- const threadId = span.metadata?.threadId;
603
- if (typeof threadId === "string" && threadId.length > 0) {
604
- attributes[incubating.ATTR_GEN_AI_CONVERSATION_ID] = threadId;
605
- }
606
- return attributes;
418
+ const attributes = {};
419
+ const spanType = span.type.toLowerCase();
420
+ attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_OPERATION_NAME] = getOperationName(span);
421
+ attributes["mastra.span.type"] = span.type;
422
+ if (span.input !== void 0) {
423
+ const inputStr = typeof span.input === "string" ? span.input : JSON.stringify(span.input);
424
+ if (span.type === _mastra_core_observability.SpanType.MODEL_GENERATION) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_INPUT_MESSAGES] = convertMastraMessagesToGenAIMessages(inputStr);
425
+ else if (span.type === _mastra_core_observability.SpanType.TOOL_CALL || span.type === _mastra_core_observability.SpanType.MCP_TOOL_CALL || span.type === _mastra_core_observability.SpanType.PROVIDER_TOOL_CALL) attributes["gen_ai.tool.call.arguments"] = inputStr;
426
+ else attributes[`mastra.${spanType}.input`] = inputStr;
427
+ }
428
+ if (span.output !== void 0) {
429
+ const outputStr = typeof span.output === "string" ? span.output : JSON.stringify(span.output);
430
+ if (span.type === _mastra_core_observability.SpanType.MODEL_GENERATION) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_OUTPUT_MESSAGES] = convertMastraMessagesToGenAIMessages(outputStr);
431
+ else if (span.type === _mastra_core_observability.SpanType.TOOL_CALL || span.type === _mastra_core_observability.SpanType.MCP_TOOL_CALL || span.type === _mastra_core_observability.SpanType.PROVIDER_TOOL_CALL) attributes["gen_ai.tool.call.result"] = outputStr;
432
+ else attributes[`mastra.${spanType}.output`] = outputStr;
433
+ }
434
+ if (span.type === _mastra_core_observability.SpanType.MODEL_GENERATION && span.attributes) {
435
+ const modelAttrs = span.attributes;
436
+ addModelRequestAttributes(attributes, modelAttrs);
437
+ if (span.entityId) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_AGENT_ID] = span.entityId;
438
+ if (span.entityName) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_AGENT_NAME] = span.entityName;
439
+ if (modelAttrs.parameters) {
440
+ if (modelAttrs.parameters.temperature !== void 0) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_REQUEST_TEMPERATURE] = modelAttrs.parameters.temperature;
441
+ if (modelAttrs.parameters.maxOutputTokens !== void 0) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_REQUEST_MAX_TOKENS] = modelAttrs.parameters.maxOutputTokens;
442
+ if (modelAttrs.parameters.topP !== void 0) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_REQUEST_TOP_P] = modelAttrs.parameters.topP;
443
+ if (modelAttrs.parameters.topK !== void 0) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_REQUEST_TOP_K] = modelAttrs.parameters.topK;
444
+ if (modelAttrs.parameters.presencePenalty !== void 0) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_REQUEST_PRESENCE_PENALTY] = modelAttrs.parameters.presencePenalty;
445
+ if (modelAttrs.parameters.frequencyPenalty !== void 0) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_REQUEST_FREQUENCY_PENALTY] = modelAttrs.parameters.frequencyPenalty;
446
+ if (modelAttrs.parameters.stopSequences) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_REQUEST_STOP_SEQUENCES] = JSON.stringify(modelAttrs.parameters.stopSequences);
447
+ if (modelAttrs.parameters.seed) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_REQUEST_SEED] = modelAttrs.parameters.seed;
448
+ }
449
+ if (modelAttrs.completionStartTime) attributes["mastra.completion_start_time"] = modelAttrs.completionStartTime.toISOString();
450
+ if (modelAttrs.finishReason) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_RESPONSE_FINISH_REASONS] = JSON.stringify([modelAttrs.finishReason]);
451
+ if (modelAttrs.responseModel) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_RESPONSE_MODEL] = modelAttrs.responseModel;
452
+ if (modelAttrs.responseId) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_RESPONSE_ID] = modelAttrs.responseId;
453
+ if (modelAttrs.serverAddress) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_SERVER_ADDRESS] = modelAttrs.serverAddress;
454
+ if (modelAttrs.serverPort !== void 0) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_SERVER_PORT] = modelAttrs.serverPort;
455
+ }
456
+ if (span.type === _mastra_core_observability.SpanType.RAG_EMBEDDING && span.attributes) {
457
+ const embeddingAttrs = span.attributes;
458
+ addModelRequestAttributes(attributes, embeddingAttrs);
459
+ if (embeddingAttrs.mode) attributes[`mastra.${spanType}.mode`] = embeddingAttrs.mode;
460
+ if (embeddingAttrs.dimensions !== void 0) {
461
+ attributes["gen_ai.embeddings.dimension.count"] = embeddingAttrs.dimensions;
462
+ attributes[`mastra.${spanType}.dimensions`] = embeddingAttrs.dimensions;
463
+ }
464
+ if (embeddingAttrs.inputCount !== void 0) attributes[`mastra.${spanType}.input_count`] = embeddingAttrs.inputCount;
465
+ }
466
+ if ((span.type === _mastra_core_observability.SpanType.TOOL_CALL || span.type === _mastra_core_observability.SpanType.MCP_TOOL_CALL || span.type === _mastra_core_observability.SpanType.PROVIDER_TOOL_CALL) && span.attributes) {
467
+ attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_TOOL_NAME] = span.entityName ?? span.entityId;
468
+ if (span.type === _mastra_core_observability.SpanType.MCP_TOOL_CALL) {
469
+ const mcpAttrs = span.attributes;
470
+ if (mcpAttrs.mcpServer) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_SERVER_ADDRESS] = mcpAttrs.mcpServer;
471
+ } else {
472
+ const toolAttrs = span.attributes;
473
+ if (toolAttrs.toolDescription) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_TOOL_DESCRIPTION] = toolAttrs.toolDescription;
474
+ if (toolAttrs.toolType) attributes["gen_ai.tool.type"] = toolAttrs.toolType;
475
+ }
476
+ }
477
+ if (span.type === _mastra_core_observability.SpanType.AGENT_RUN && span.attributes) {
478
+ const agentAttrs = span.attributes;
479
+ if (span.entityId) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_AGENT_ID] = span.entityId;
480
+ if (span.entityName) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_AGENT_NAME] = span.entityName;
481
+ if (agentAttrs.conversationId) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_CONVERSATION_ID] = agentAttrs.conversationId;
482
+ if (agentAttrs.maxSteps) attributes[`mastra.${spanType}.max_steps`] = agentAttrs.maxSteps;
483
+ if (agentAttrs.availableTools) attributes[`gen_ai.tool.definitions`] = JSON.stringify(agentAttrs.availableTools);
484
+ attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_SYSTEM_INSTRUCTIONS] = agentAttrs.instructions;
485
+ }
486
+ if (span.errorInfo) {
487
+ attributes[_opentelemetry_semantic_conventions_incubating.ATTR_ERROR_TYPE] = span.errorInfo.id || "unknown";
488
+ attributes[_opentelemetry_semantic_conventions_incubating.ATTR_ERROR_MESSAGE] = span.errorInfo.message;
489
+ if (span.errorInfo.domain) attributes["error.domain"] = span.errorInfo.domain;
490
+ if (span.errorInfo.category) attributes["error.category"] = span.errorInfo.category;
491
+ }
492
+ const threadId = span.metadata?.threadId;
493
+ if (typeof threadId === "string" && threadId.length > 0) attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_CONVERSATION_ID] = threadId;
494
+ return attributes;
607
495
  }
608
- var PROVIDER_ALIASES = {
609
- anthropic: ["anthropic", "claude"],
610
- "aws.bedrock": ["awsbedrock", "bedrock", "amazonbedrock"],
611
- "azure.ai.inference": ["azureaiinference", "azureinference"],
612
- "azure.ai.openai": ["azureaiopenai", "azureopenai", "msopenai", "microsoftopenai"],
613
- cohere: ["cohere"],
614
- deepseek: ["deepseek"],
615
- "gcp.gemini": ["gcpgemini", "gemini"],
616
- "gcp.gen_ai": ["gcpgenai", "googlegenai", "googleai"],
617
- "gcp.vertex_ai": ["gcpvertexai", "vertexai"],
618
- groq: ["groq"],
619
- "ibm.watsonx.ai": ["ibmwatsonxai", "watsonx", "watsonxai"],
620
- mistral_ai: ["mistral", "mistralai"],
621
- openai: ["openai", "oai"],
622
- perplexity: ["perplexity", "pplx"],
623
- x_ai: ["xai", "x-ai", "x_ai", "x.com ai"]
496
+ /**
497
+ * Canonical OTel provider keys mapped to a list of possible fuzzy aliases.
498
+ */
499
+ const PROVIDER_ALIASES = {
500
+ anthropic: ["anthropic", "claude"],
501
+ "aws.bedrock": [
502
+ "awsbedrock",
503
+ "bedrock",
504
+ "amazonbedrock"
505
+ ],
506
+ "azure.ai.inference": ["azureaiinference", "azureinference"],
507
+ "azure.ai.openai": [
508
+ "azureaiopenai",
509
+ "azureopenai",
510
+ "msopenai",
511
+ "microsoftopenai"
512
+ ],
513
+ cohere: ["cohere"],
514
+ deepseek: ["deepseek"],
515
+ "gcp.gemini": ["gcpgemini", "gemini"],
516
+ "gcp.gen_ai": [
517
+ "gcpgenai",
518
+ "googlegenai",
519
+ "googleai"
520
+ ],
521
+ "gcp.vertex_ai": ["gcpvertexai", "vertexai"],
522
+ groq: ["groq"],
523
+ "ibm.watsonx.ai": [
524
+ "ibmwatsonxai",
525
+ "watsonx",
526
+ "watsonxai"
527
+ ],
528
+ mistral_ai: ["mistral", "mistralai"],
529
+ openai: ["openai", "oai"],
530
+ perplexity: ["perplexity", "pplx"],
531
+ x_ai: [
532
+ "xai",
533
+ "x-ai",
534
+ "x_ai",
535
+ "x.com ai"
536
+ ]
624
537
  };
538
+ /**
539
+ * Normalize a provider input string into a matchable token.
540
+ * Keep only alphanumerics and lowercase the result.
541
+ */
625
542
  function normalizeProviderString(input) {
626
- return input.toLowerCase().replace(/[^a-z0-9]/g, "");
543
+ return input.toLowerCase().replace(/[^a-z0-9]/g, "");
627
544
  }
545
+ /**
546
+ * Attempts to map a providerName to one of the canonical OTel provider names.
547
+ * If no match is found, returns the original providerName unchanged.
548
+ */
628
549
  function normalizeProvider(providerName) {
629
- const normalized = normalizeProviderString(providerName);
630
- for (const [canonical, aliases] of Object.entries(PROVIDER_ALIASES)) {
631
- for (const alias of aliases) {
632
- if (normalized === alias) {
633
- return canonical;
634
- }
635
- }
636
- }
637
- return providerName.toLowerCase();
550
+ const normalized = normalizeProviderString(providerName);
551
+ for (const [canonical, aliases] of Object.entries(PROVIDER_ALIASES)) for (const alias of aliases) if (normalized === alias) return canonical;
552
+ return providerName.toLowerCase();
638
553
  }
639
-
640
- // src/span-converter.ts
554
+ //#endregion
555
+ //#region src/span-converter.ts
556
+ /**
557
+ * Convert Mastra Spans to OpenTelemetry spans
558
+ */
641
559
  var SpanConverter = class {
642
- constructor(params) {
643
- this.params = params;
644
- this.format = params.format;
645
- }
646
- params;
647
- resource;
648
- scope;
649
- initPromise;
650
- format;
651
- /**
652
- * Lazily initialize resource & scope on first use.
653
- * Subsequent calls reuse the same promise (no races).
654
- */
655
- async initIfNeeded() {
656
- if (this.initPromise) {
657
- return this.initPromise;
658
- }
659
- this.initPromise = (async () => {
660
- const packageVersion = await getPackageVersion(this.params.packageName) ?? "unknown";
661
- const serviceVersion = await getPackageVersion("@mastra/core") ?? "unknown";
662
- let resource = resources.resourceFromAttributes({
663
- [semanticConventions.ATTR_SERVICE_NAME]: this.params.serviceName || "mastra-service",
664
- [semanticConventions.ATTR_SERVICE_VERSION]: serviceVersion,
665
- [semanticConventions.ATTR_TELEMETRY_SDK_NAME]: this.params.packageName,
666
- [semanticConventions.ATTR_TELEMETRY_SDK_VERSION]: packageVersion,
667
- [semanticConventions.ATTR_TELEMETRY_SDK_LANGUAGE]: "nodejs"
668
- });
669
- if (this.params.config?.resourceAttributes) {
670
- resource = resource.merge(
671
- // Duplicate attributes from config will override defaults above
672
- resources.resourceFromAttributes(this.params.config.resourceAttributes)
673
- );
674
- }
675
- this.resource = resource;
676
- this.scope = {
677
- name: this.params.packageName,
678
- version: packageVersion
679
- };
680
- })();
681
- return this.initPromise;
682
- }
683
- /**
684
- * Convert a Mastra Span to an OpenTelemetry ReadableSpan
685
- */
686
- async convertSpan(span) {
687
- await this.initIfNeeded();
688
- if (!this.resource || !this.scope) {
689
- throw new Error("SpanConverter not initialized correctly");
690
- }
691
- const name = getSpanName(span);
692
- const kind = getSpanKind(span.type);
693
- const attributes = getAttributes(span);
694
- if (span.metadata) {
695
- for (const [k, v] of Object.entries(span.metadata)) {
696
- if (v === null || v === void 0) {
697
- continue;
698
- }
699
- attributes[`mastra.metadata.${k}`] = typeof v === "object" ? JSON.stringify(v) : v;
700
- }
701
- }
702
- if (span.isRootSpan && span.tags?.length) {
703
- attributes["mastra.tags"] = JSON.stringify(span.tags);
704
- }
705
- const startTime = dateToHrTime2(span.startTime);
706
- const endTime = span.endTime ? dateToHrTime2(span.endTime) : startTime;
707
- const duration = computeDuration(span.startTime, span.endTime);
708
- const { status, events } = buildStatusAndEvents(span, startTime);
709
- const spanContext = {
710
- traceId: span.traceId,
711
- spanId: span.id,
712
- traceFlags: api.TraceFlags.SAMPLED,
713
- isRemote: false
714
- };
715
- const parentSpanContext = span.parentSpanId ? {
716
- traceId: span.traceId,
717
- spanId: span.parentSpanId,
718
- traceFlags: api.TraceFlags.SAMPLED,
719
- isRemote: false
720
- } : void 0;
721
- const links = [];
722
- const readable = {
723
- name,
724
- kind,
725
- spanContext: () => spanContext,
726
- parentSpanContext,
727
- startTime,
728
- endTime,
729
- status,
730
- attributes,
731
- links,
732
- events,
733
- duration,
734
- ended: !!span.endTime,
735
- resource: this.resource,
736
- instrumentationScope: this.scope,
737
- droppedAttributesCount: 0,
738
- droppedEventsCount: 0,
739
- droppedLinksCount: 0
740
- };
741
- return readable;
742
- }
560
+ params;
561
+ resource;
562
+ scope;
563
+ initPromise;
564
+ format;
565
+ constructor(params) {
566
+ this.params = params;
567
+ this.format = params.format;
568
+ }
569
+ /**
570
+ * Lazily initialize resource & scope on first use.
571
+ * Subsequent calls reuse the same promise (no races).
572
+ */
573
+ async initIfNeeded() {
574
+ if (this.initPromise) return this.initPromise;
575
+ this.initPromise = (async () => {
576
+ const packageVersion = await getPackageVersion(this.params.packageName) ?? "unknown";
577
+ const serviceVersion = await getPackageVersion("@mastra/core") ?? "unknown";
578
+ let resource = (0, _opentelemetry_resources.resourceFromAttributes)({
579
+ [_opentelemetry_semantic_conventions.ATTR_SERVICE_NAME]: this.params.serviceName || "mastra-service",
580
+ [_opentelemetry_semantic_conventions.ATTR_SERVICE_VERSION]: serviceVersion,
581
+ [_opentelemetry_semantic_conventions.ATTR_TELEMETRY_SDK_NAME]: this.params.packageName,
582
+ [_opentelemetry_semantic_conventions.ATTR_TELEMETRY_SDK_VERSION]: packageVersion,
583
+ [_opentelemetry_semantic_conventions.ATTR_TELEMETRY_SDK_LANGUAGE]: "nodejs"
584
+ });
585
+ if (this.params.config?.resourceAttributes) resource = resource.merge((0, _opentelemetry_resources.resourceFromAttributes)(this.params.config.resourceAttributes));
586
+ this.resource = resource;
587
+ this.scope = {
588
+ name: this.params.packageName,
589
+ version: packageVersion
590
+ };
591
+ })();
592
+ return this.initPromise;
593
+ }
594
+ /**
595
+ * Convert a Mastra Span to an OpenTelemetry ReadableSpan
596
+ */
597
+ async convertSpan(span) {
598
+ await this.initIfNeeded();
599
+ if (!this.resource || !this.scope) throw new Error("SpanConverter not initialized correctly");
600
+ const name = getSpanName(span);
601
+ const kind = getSpanKind(span.type);
602
+ const attributes = getAttributes(span);
603
+ if (span.metadata) for (const [k, v] of Object.entries(span.metadata)) {
604
+ if (v === null || v === void 0) continue;
605
+ attributes[`mastra.metadata.${k}`] = typeof v === "object" ? JSON.stringify(v) : v;
606
+ }
607
+ if (span.isRootSpan && span.tags?.length) attributes["mastra.tags"] = JSON.stringify(span.tags);
608
+ const startTime = dateToHrTime(span.startTime);
609
+ const endTime = span.endTime ? dateToHrTime(span.endTime) : startTime;
610
+ const duration = computeDuration(span.startTime, span.endTime);
611
+ const { status, events } = buildStatusAndEvents(span, startTime);
612
+ const spanContext = {
613
+ traceId: span.traceId,
614
+ spanId: span.id,
615
+ traceFlags: _opentelemetry_api.TraceFlags.SAMPLED,
616
+ isRemote: false
617
+ };
618
+ return {
619
+ name,
620
+ kind,
621
+ spanContext: () => spanContext,
622
+ parentSpanContext: span.parentSpanId ? {
623
+ traceId: span.traceId,
624
+ spanId: span.parentSpanId,
625
+ traceFlags: _opentelemetry_api.TraceFlags.SAMPLED,
626
+ isRemote: false
627
+ } : void 0,
628
+ startTime,
629
+ endTime,
630
+ status,
631
+ attributes,
632
+ links: [],
633
+ events,
634
+ duration,
635
+ ended: !!span.endTime,
636
+ resource: this.resource,
637
+ instrumentationScope: this.scope,
638
+ droppedAttributesCount: 0,
639
+ droppedEventsCount: 0,
640
+ droppedLinksCount: 0
641
+ };
642
+ }
743
643
  };
744
644
  async function getPackageVersion(pkgName) {
745
- try {
746
- const manifestUrl = new URL(await undefined(`${pkgName}/package.json`));
747
- const path = url.fileURLToPath(manifestUrl);
748
- const pkgJson = JSON.parse(fs.readFileSync(path, "utf8"));
749
- return pkgJson.version;
750
- } catch {
751
- return void 0;
752
- }
645
+ try {
646
+ const path = (0, url.fileURLToPath)(new URL(await {}.resolve(`${pkgName}/package.json`)));
647
+ return JSON.parse((0, fs.readFileSync)(path, "utf8")).version;
648
+ } catch {
649
+ return;
650
+ }
753
651
  }
652
+ /**
653
+ * Get the appropriate Otel SpanKind based on Mastra SpanType.
654
+ *
655
+ * @param type - The Mastra span type
656
+ * @returns The appropriate OTEL SpanKind
657
+ */
754
658
  function getSpanKind(type) {
755
- switch (type) {
756
- case observability.SpanType.MODEL_GENERATION:
757
- case observability.SpanType.RAG_EMBEDDING:
758
- case observability.SpanType.MCP_TOOL_CALL:
759
- return api.SpanKind.CLIENT;
760
- default:
761
- return api.SpanKind.INTERNAL;
762
- }
659
+ switch (type) {
660
+ case _mastra_core_observability.SpanType.MODEL_GENERATION:
661
+ case _mastra_core_observability.SpanType.RAG_EMBEDDING:
662
+ case _mastra_core_observability.SpanType.MCP_TOOL_CALL: return _opentelemetry_api.SpanKind.CLIENT;
663
+ default: return _opentelemetry_api.SpanKind.INTERNAL;
664
+ }
763
665
  }
764
- function dateToHrTime2(date) {
765
- const ms = date.getTime();
766
- const seconds = Math.floor(ms / 1e3);
767
- const nanoseconds = ms % 1e3 * 1e6;
768
- return [seconds, nanoseconds];
666
+ /**
667
+ * Convert JavaScript Date to hrtime format
668
+ */
669
+ function dateToHrTime(date) {
670
+ const ms = date.getTime();
671
+ return [Math.floor(ms / 1e3), ms % 1e3 * 1e6];
769
672
  }
770
673
  function computeDuration(start, end) {
771
- if (!end) return [0, 0];
772
- const diffMs = end.getTime() - start.getTime();
773
- return [Math.floor(diffMs / 1e3), diffMs % 1e3 * 1e6];
674
+ if (!end) return [0, 0];
675
+ const diffMs = end.getTime() - start.getTime();
676
+ return [Math.floor(diffMs / 1e3), diffMs % 1e3 * 1e6];
774
677
  }
678
+ /**
679
+ * Build status + events from span.errorInfo (if present)
680
+ */
775
681
  function buildStatusAndEvents(span, defaultTime) {
776
- const events = [];
777
- if (span.errorInfo) {
778
- const status = {
779
- code: api.SpanStatusCode.ERROR,
780
- message: span.errorInfo.message
781
- };
782
- events.push({
783
- name: "exception",
784
- attributes: {
785
- "exception.message": span.errorInfo.message,
786
- "exception.type": "Error",
787
- ...span.errorInfo.details?.stack && {
788
- "exception.stacktrace": span.errorInfo.details.stack
789
- }
790
- },
791
- time: defaultTime,
792
- droppedAttributesCount: 0
793
- });
794
- return { status, events };
795
- }
796
- if (span.endTime) {
797
- return {
798
- status: { code: api.SpanStatusCode.OK },
799
- events
800
- };
801
- }
802
- return {
803
- status: { code: api.SpanStatusCode.UNSET },
804
- events
805
- };
682
+ const events = [];
683
+ if (span.errorInfo) {
684
+ const status = {
685
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
686
+ message: span.errorInfo.message
687
+ };
688
+ events.push({
689
+ name: "exception",
690
+ attributes: {
691
+ "exception.message": span.errorInfo.message,
692
+ "exception.type": "Error",
693
+ ...span.errorInfo.details?.stack && { "exception.stacktrace": span.errorInfo.details.stack }
694
+ },
695
+ time: defaultTime,
696
+ droppedAttributesCount: 0
697
+ });
698
+ return {
699
+ status,
700
+ events
701
+ };
702
+ }
703
+ if (span.endTime) return {
704
+ status: { code: _opentelemetry_api.SpanStatusCode.OK },
705
+ events
706
+ };
707
+ return {
708
+ status: { code: _opentelemetry_api.SpanStatusCode.UNSET },
709
+ events
710
+ };
806
711
  }
807
-
808
- // src/tracing.ts
712
+ //#endregion
713
+ //#region src/tracing.ts
714
+ /**
715
+ * Wrapper around a SpanExporter that logs export results when debug mode is enabled.
716
+ * The OTel SDK intentionally does not log on success, making debugging difficult.
717
+ * This wrapper adds visibility into each batch export's outcome.
718
+ */
809
719
  var DebugSpanExporterWrapper = class {
810
- constructor(inner, debugLog) {
811
- this.inner = inner;
812
- this.debugLog = debugLog;
813
- }
814
- inner;
815
- debugLog;
816
- export(spans, resultCallback) {
817
- const count = spans.length;
818
- this.inner.export(spans, (result) => {
819
- if (result.code === core.ExportResultCode.SUCCESS) {
820
- this.debugLog(`[OtelExporter] Export completed: ${count} spans sent successfully`);
821
- } else {
822
- this.debugLog(`[OtelExporter] Export FAILED: ${count} spans, error: ${result.error?.message ?? "unknown"}`);
823
- }
824
- resultCallback(result);
825
- });
826
- }
827
- async shutdown() {
828
- return this.inner.shutdown();
829
- }
830
- async forceFlush() {
831
- return this.inner.forceFlush?.();
832
- }
720
+ inner;
721
+ debugLog;
722
+ constructor(inner, debugLog) {
723
+ this.inner = inner;
724
+ this.debugLog = debugLog;
725
+ }
726
+ export(spans, resultCallback) {
727
+ const count = spans.length;
728
+ this.inner.export(spans, (result) => {
729
+ if (result.code === _opentelemetry_core.ExportResultCode.SUCCESS) this.debugLog(`[OtelExporter] Export completed: ${count} spans sent successfully`);
730
+ else this.debugLog(`[OtelExporter] Export FAILED: ${count} spans, error: ${result.error?.message ?? "unknown"}`);
731
+ resultCallback(result);
732
+ });
733
+ }
734
+ async shutdown() {
735
+ return this.inner.shutdown();
736
+ }
737
+ async forceFlush() {
738
+ return this.inner.forceFlush?.();
739
+ }
833
740
  };
741
+ /**
742
+ * Wrapper around a LogRecordExporter that logs export results when debug mode is enabled.
743
+ * Same pattern as DebugSpanExporterWrapper but for log records.
744
+ */
834
745
  var DebugLogExporterWrapper = class {
835
- constructor(inner, debugLog) {
836
- this.inner = inner;
837
- this.debugLog = debugLog;
838
- }
839
- inner;
840
- debugLog;
841
- export(logs, resultCallback) {
842
- const count = logs.length;
843
- this.inner.export(logs, (result) => {
844
- if (result.code === core.ExportResultCode.SUCCESS) {
845
- this.debugLog(`[OtelExporter] Log export completed: ${count} logs sent successfully`);
846
- } else {
847
- this.debugLog(`[OtelExporter] Log export FAILED: ${count} logs, error: ${result.error?.message ?? "unknown"}`);
848
- }
849
- resultCallback(result);
850
- });
851
- }
852
- async shutdown() {
853
- return this.inner.shutdown();
854
- }
855
- async forceFlush() {
856
- return this.inner.forceFlush?.();
857
- }
746
+ inner;
747
+ debugLog;
748
+ constructor(inner, debugLog) {
749
+ this.inner = inner;
750
+ this.debugLog = debugLog;
751
+ }
752
+ export(logs, resultCallback) {
753
+ const count = logs.length;
754
+ this.inner.export(logs, (result) => {
755
+ if (result.code === _opentelemetry_core.ExportResultCode.SUCCESS) this.debugLog(`[OtelExporter] Log export completed: ${count} logs sent successfully`);
756
+ else this.debugLog(`[OtelExporter] Log export FAILED: ${count} logs, error: ${result.error?.message ?? "unknown"}`);
757
+ resultCallback(result);
758
+ });
759
+ }
760
+ async shutdown() {
761
+ return this.inner.shutdown();
762
+ }
763
+ async forceFlush() {
764
+ return this.inner.forceFlush?.();
765
+ }
858
766
  };
859
- var OtelExporter = class extends observability$1.BaseExporter {
860
- config;
861
- observabilityConfig;
862
- // Trace signal
863
- spanConverter;
864
- processor;
865
- exporter;
866
- // Log signal
867
- loggerProvider;
868
- otelLogger;
869
- // Provider config (resolved once, shared across signals)
870
- resolvedConfig;
871
- providerName;
872
- // Single setup promise — all signals initialize together at init() time.
873
- // Event handlers await this before processing. Never rejects.
874
- setupPromise;
875
- name = "opentelemetry";
876
- constructor(config) {
877
- super(config);
878
- this.config = config;
879
- if (config.logLevel === "debug") {
880
- api.diag.setLogger(new api.DiagConsoleLogger(), api.DiagLogLevel.INFO);
881
- }
882
- }
883
- /**
884
- * Initialize with observability configuration and eagerly set up the trace
885
- * and log signal exporters in parallel.
886
- *
887
- * Called by Mastra during component registration. The async setup runs in the
888
- * background — event handlers await the resulting promise before processing.
889
- */
890
- init(options) {
891
- this.observabilityConfig = options.config;
892
- this.setupPromise = this.setupAllSignals();
893
- }
894
- // ===========================================================================
895
- // Provider config resolution (shared across all signals)
896
- // ===========================================================================
897
- get isDebug() {
898
- return this.config.logLevel === "debug";
899
- }
900
- /**
901
- * Debug logging that bypasses the Mastra logger.
902
- *
903
- * The Mastra framework replaces our logger via __setLogger() with one at
904
- * INFO level, which silently swallows all this.logger.debug() calls.
905
- * For debug output we need to go through console.info directly.
906
- */
907
- debugLog = (...args) => this.isDebug && console.info("[OtelExporter:debug]", ...args);
908
- resolveProvider() {
909
- if (this.resolvedConfig !== void 0) {
910
- return this.resolvedConfig;
911
- }
912
- if (!this.config.provider) {
913
- this.setDisabled(
914
- '[OtelExporter] Provider configuration is required. Use the "custom" provider for generic endpoints.'
915
- );
916
- this.resolvedConfig = null;
917
- return null;
918
- }
919
- this.providerName = Object.keys(this.config.provider)[0];
920
- const resolved = resolveProviderConfig(this.config.provider, this.isDebug);
921
- if (!resolved) {
922
- this.setDisabled("[OtelExporter] Provider configuration validation failed.");
923
- this.resolvedConfig = null;
924
- return null;
925
- }
926
- this.resolvedConfig = resolved;
927
- return resolved;
928
- }
929
- /**
930
- * Derive the endpoint for a specific signal from the resolved provider config.
931
- * Strips trailing slashes and any existing signal-path suffix so we never
932
- * produce a doubled "//v1/logs" or leave a stale "/v1/traces" on a logs URL.
933
- */
934
- getSignalEndpoint(resolved, signal) {
935
- const signalPaths = {
936
- traces: "/v1/traces",
937
- logs: "/v1/logs"
938
- };
939
- let base = resolved.endpoint;
940
- while (base.endsWith("/")) {
941
- base = base.slice(0, -1);
942
- }
943
- for (const path of Object.values(signalPaths)) {
944
- if (base.endsWith(path)) {
945
- base = base.slice(0, -path.length);
946
- break;
947
- }
948
- }
949
- return base + signalPaths[signal];
950
- }
951
- /**
952
- * Build exporter constructor options for the given signal endpoint.
953
- * For gRPC, converts headers to Metadata. For HTTP protocols, passes headers directly.
954
- */
955
- async buildExporterOptions(protocol, url, headers) {
956
- if (protocol === "grpc") {
957
- const grpcModule = await import('@grpc/grpc-js');
958
- const metadata = new grpcModule.Metadata();
959
- for (const [key, value] of Object.entries(headers)) {
960
- metadata.set(key, value);
961
- }
962
- return { url, metadata, timeoutMillis: this.config.timeout };
963
- }
964
- return { url, headers, timeoutMillis: this.config.timeout };
965
- }
966
- // ===========================================================================
967
- // Setup — eager, parallel initialization of all signals
968
- // ===========================================================================
969
- /**
970
- * Wait for setup to complete. If init() was called, this awaits the
971
- * already-in-flight setup promise. If init() was never called (standalone
972
- * usage without Mastra), this triggers setup on first use.
973
- */
974
- ensureSetup() {
975
- if (!this.setupPromise) {
976
- this.setupPromise = this.setupAllSignals();
977
- }
978
- return this.setupPromise;
979
- }
980
- /**
981
- * Resolve provider config once and set up all enabled signals in parallel.
982
- * Each signal setup catches its own errors — this method never rejects.
983
- */
984
- async setupAllSignals() {
985
- const resolved = this.resolveProvider();
986
- if (!resolved) {
987
- this.debugLog("Setup skipped: provider not resolved");
988
- return;
989
- }
990
- const protocol = resolved.protocol;
991
- const setupTasks = [];
992
- if (this.config.signals?.traces !== false) {
993
- setupTasks.push(this.setupTraces(resolved, protocol));
994
- } else {
995
- this.debugLog("Trace export disabled via config");
996
- }
997
- if (this.config.signals?.logs !== false) {
998
- setupTasks.push(this.setupLogs(resolved, protocol));
999
- } else {
1000
- this.debugLog("Log export disabled via config");
1001
- }
1002
- await Promise.all(setupTasks);
1003
- this.debugLog(`Setup complete [traces=${!!this.processor}, logs=${!!this.otelLogger}]`);
1004
- }
1005
- // ---------------------------------------------------------------------------
1006
- // Trace setup
1007
- // ---------------------------------------------------------------------------
1008
- async setupTraces(resolved, protocol) {
1009
- try {
1010
- if (this.config.exporter) {
1011
- this.exporter = this.config.exporter;
1012
- } else {
1013
- const headers = resolved.headers;
1014
- const endpoint = protocol === "zipkin" ? resolved.endpoint : this.getSignalEndpoint(resolved, "traces");
1015
- this.debugLog(`Setting up trace exporter: protocol=${protocol}, endpoint=${endpoint}`);
1016
- const ExporterClass = await loadExporter(protocol, this.providerName);
1017
- if (!ExporterClass) {
1018
- this.debugLog(`Trace exporter not available for protocol: ${protocol}`);
1019
- return;
1020
- }
1021
- const exporterOptions = protocol === "zipkin" ? { url: endpoint, headers } : await this.buildExporterOptions(protocol, endpoint, headers);
1022
- this.exporter = new ExporterClass(exporterOptions);
1023
- this.debugLog(`Trace exporter created: ${this.exporter?.constructor?.name ?? "unknown"} -> ${endpoint}`);
1024
- }
1025
- const serviceName = this.observabilityConfig?.serviceName || "mastra-service";
1026
- this.spanConverter = new SpanConverter({
1027
- packageName: "@mastra/otel-exporter",
1028
- serviceName,
1029
- config: this.config,
1030
- format: "GenAI_v1_38_0"
1031
- });
1032
- const exporterForProcessor = this.isDebug ? new DebugSpanExporterWrapper(this.exporter, (msg) => this.debugLog(msg)) : this.exporter;
1033
- this.processor = new sdkTraceBase.BatchSpanProcessor(exporterForProcessor, {
1034
- maxExportBatchSize: this.config.batchSize || 512,
1035
- maxQueueSize: 2048,
1036
- scheduledDelayMillis: 5e3,
1037
- exportTimeoutMillis: this.config.timeout || 3e4
1038
- });
1039
- this.debugLog(
1040
- `Trace export initialized (service.name: "${serviceName}", batch size: ${this.config.batchSize || 512}, delay: 5s)`
1041
- );
1042
- } catch (error) {
1043
- this.logger.warn("[OtelExporter] Failed to initialize trace export");
1044
- this.debugLog("Trace setup error:", error);
1045
- }
1046
- }
1047
- // ---------------------------------------------------------------------------
1048
- // Log setup
1049
- // ---------------------------------------------------------------------------
1050
- async setupLogs(resolved, protocol) {
1051
- try {
1052
- const LogExporterClass = await loadSignalExporter("logs", protocol, this.providerName);
1053
- if (!LogExporterClass) {
1054
- this.debugLog(`Log exporter package not available for protocol "${protocol}". Log export disabled.`);
1055
- return;
1056
- }
1057
- const logEndpoint = this.getSignalEndpoint(resolved, "logs");
1058
- const headers = resolved.headers;
1059
- this.debugLog(`Setting up log exporter: protocol=${protocol}, endpoint=${logEndpoint}`);
1060
- const logExporterOptions = await this.buildExporterOptions(protocol, logEndpoint, headers);
1061
- const logExporter = new LogExporterClass(logExporterOptions);
1062
- const exporterForProcessor = this.isDebug ? new DebugLogExporterWrapper(logExporter, (msg) => this.debugLog(msg)) : logExporter;
1063
- const resource = resources.resourceFromAttributes({
1064
- ...this.config.resourceAttributes ?? {},
1065
- [semanticConventions.ATTR_SERVICE_NAME]: this.observabilityConfig?.serviceName || "mastra-service"
1066
- });
1067
- this.loggerProvider = new sdkLogs.LoggerProvider({
1068
- resource,
1069
- processors: [
1070
- new sdkLogs.BatchLogRecordProcessor(exporterForProcessor, {
1071
- maxExportBatchSize: this.config.batchSize || 512,
1072
- maxQueueSize: 2048,
1073
- scheduledDelayMillis: 5e3,
1074
- exportTimeoutMillis: this.config.timeout || 3e4
1075
- })
1076
- ]
1077
- });
1078
- this.otelLogger = this.loggerProvider.getLogger("@mastra/otel-exporter");
1079
- this.debugLog(`Log export initialized (endpoint: ${logEndpoint})`);
1080
- } catch (error) {
1081
- this.logger.warn("[OtelExporter] Failed to initialize log export");
1082
- this.debugLog("Log setup error:", error);
1083
- }
1084
- }
1085
- // ===========================================================================
1086
- // Trace event handler
1087
- // ===========================================================================
1088
- async _exportTracingEvent(event) {
1089
- if (this.config.signals?.traces === false) return;
1090
- if (event.type !== observability.TracingEventType.SPAN_ENDED) return;
1091
- await this.ensureSetup();
1092
- if (this.isDisabled || !this.processor) return;
1093
- const span = event.exportedSpan;
1094
- try {
1095
- const otelSpan = await this.spanConverter.convertSpan(span);
1096
- this.processor.onEnd(otelSpan);
1097
- this.debugLog(
1098
- `Queued span ${span.id} (trace: ${span.traceId}, parent: ${span.parentSpanId || "none"}, type: ${span.type})`
1099
- );
1100
- } catch (error) {
1101
- this.logger.error(`[OtelExporter] Failed to export span ${span.id}:`, error);
1102
- }
1103
- }
1104
- // ===========================================================================
1105
- // Log event handler
1106
- // ===========================================================================
1107
- async onLogEvent(event) {
1108
- this.debugLog(`onLogEvent received (level: ${event.log.level}, message: "${event.log.message}")`);
1109
- await this.ensureSetup();
1110
- if (this.isDisabled) {
1111
- this.debugLog("Log event skipped: exporter is disabled");
1112
- return;
1113
- }
1114
- if (!this.otelLogger) {
1115
- this.debugLog("Log event skipped: log exporter not available");
1116
- return;
1117
- }
1118
- try {
1119
- const logParams = convertLog(event.log);
1120
- const attributes = { ...logParams.attributes };
1121
- let logContext = api.context.active();
1122
- if (logParams.traceId && logParams.spanId) {
1123
- attributes["mastra.traceId"] = logParams.traceId;
1124
- attributes["mastra.spanId"] = logParams.spanId;
1125
- logContext = api.trace.setSpanContext(logContext, {
1126
- traceId: logParams.traceId,
1127
- spanId: logParams.spanId,
1128
- traceFlags: api.TraceFlags.SAMPLED,
1129
- isRemote: false
1130
- });
1131
- }
1132
- this.otelLogger.emit({
1133
- timestamp: logParams.timestamp,
1134
- severityNumber: logParams.severityNumber,
1135
- severityText: logParams.severityText,
1136
- body: logParams.body,
1137
- attributes,
1138
- context: logContext
1139
- });
1140
- this.debugLog(`Exported log (level: ${event.log.level}, trace: ${event.log.traceId || "none"})`);
1141
- } catch (error) {
1142
- this.logger.error("[OtelExporter] Failed to export log:", error);
1143
- }
1144
- }
1145
- // ===========================================================================
1146
- // Lifecycle
1147
- // ===========================================================================
1148
- /**
1149
- * Force flush any buffered data without shutting down the exporter.
1150
- * Delegates to all active processors/providers.
1151
- */
1152
- async flush() {
1153
- if (this.setupPromise) {
1154
- await this.setupPromise;
1155
- }
1156
- const flushPromises = [];
1157
- const signals = [];
1158
- if (this.processor) {
1159
- flushPromises.push(this.processor.forceFlush());
1160
- signals.push("traces");
1161
- }
1162
- if (this.loggerProvider) {
1163
- flushPromises.push(this.loggerProvider.forceFlush());
1164
- signals.push("logs");
1165
- }
1166
- if (flushPromises.length > 0) {
1167
- this.debugLog(`Flushing signals: ${signals.join(", ")}...`);
1168
- await Promise.all(flushPromises);
1169
- this.debugLog("Flushed all pending data");
1170
- } else {
1171
- this.debugLog("Flush called but no active exporters");
1172
- }
1173
- }
1174
- async shutdown() {
1175
- if (this.setupPromise) {
1176
- await this.setupPromise;
1177
- }
1178
- const shutdownPromises = [];
1179
- if (this.processor) {
1180
- shutdownPromises.push(this.processor.shutdown());
1181
- }
1182
- if (this.loggerProvider) {
1183
- shutdownPromises.push(this.loggerProvider.shutdown());
1184
- }
1185
- if (shutdownPromises.length > 0) {
1186
- await Promise.all(shutdownPromises);
1187
- }
1188
- }
767
+ var OtelExporter = class extends _mastra_observability.BaseExporter {
768
+ config;
769
+ observabilityConfig;
770
+ spanConverter;
771
+ processor;
772
+ exporter;
773
+ loggerProvider;
774
+ otelLogger;
775
+ resolvedConfig;
776
+ providerName;
777
+ setupPromise;
778
+ name = "opentelemetry";
779
+ constructor(config) {
780
+ super(config);
781
+ this.config = config;
782
+ if (config.logLevel === "debug") _opentelemetry_api.diag.setLogger(new _opentelemetry_api.DiagConsoleLogger(), _opentelemetry_api.DiagLogLevel.INFO);
783
+ }
784
+ /**
785
+ * Initialize with observability configuration and eagerly set up the trace
786
+ * and log signal exporters in parallel.
787
+ *
788
+ * Called by Mastra during component registration. The async setup runs in the
789
+ * background — event handlers await the resulting promise before processing.
790
+ */
791
+ init(options) {
792
+ this.observabilityConfig = options.config;
793
+ this.setupPromise = this.setupAllSignals();
794
+ }
795
+ get isDebug() {
796
+ return this.config.logLevel === "debug";
797
+ }
798
+ /**
799
+ * Debug logging that bypasses the Mastra logger.
800
+ *
801
+ * The Mastra framework replaces our logger via __setLogger() with one at
802
+ * INFO level, which silently swallows all this.logger.debug() calls.
803
+ * For debug output we need to go through console.info directly.
804
+ */
805
+ debugLog = (...args) => this.isDebug && console.info("[OtelExporter:debug]", ...args);
806
+ resolveProvider() {
807
+ if (this.resolvedConfig !== void 0) return this.resolvedConfig;
808
+ if (!this.config.provider) {
809
+ this.setDisabled("[OtelExporter] Provider configuration is required. Use the \"custom\" provider for generic endpoints.");
810
+ this.resolvedConfig = null;
811
+ return null;
812
+ }
813
+ this.providerName = Object.keys(this.config.provider)[0];
814
+ const resolved = resolveProviderConfig(this.config.provider, this.isDebug);
815
+ if (!resolved) {
816
+ this.setDisabled("[OtelExporter] Provider configuration validation failed.");
817
+ this.resolvedConfig = null;
818
+ return null;
819
+ }
820
+ this.resolvedConfig = resolved;
821
+ return resolved;
822
+ }
823
+ /**
824
+ * Derive the endpoint for a specific signal from the resolved provider config.
825
+ * Strips trailing slashes and any existing signal-path suffix so we never
826
+ * produce a doubled "//v1/logs" or leave a stale "/v1/traces" on a logs URL.
827
+ */
828
+ getSignalEndpoint(resolved, signal) {
829
+ const signalPaths = {
830
+ traces: "/v1/traces",
831
+ logs: "/v1/logs"
832
+ };
833
+ let base = resolved.endpoint;
834
+ while (base.endsWith("/")) base = base.slice(0, -1);
835
+ for (const path of Object.values(signalPaths)) if (base.endsWith(path)) {
836
+ base = base.slice(0, -path.length);
837
+ break;
838
+ }
839
+ return base + signalPaths[signal];
840
+ }
841
+ /**
842
+ * Build exporter constructor options for the given signal endpoint.
843
+ * For gRPC, converts headers to Metadata. For HTTP protocols, passes headers directly.
844
+ */
845
+ async buildExporterOptions(protocol, url, headers) {
846
+ if (protocol === "grpc") {
847
+ const metadata = new (await (import("@grpc/grpc-js"))).Metadata();
848
+ for (const [key, value] of Object.entries(headers)) metadata.set(key, value);
849
+ return {
850
+ url,
851
+ metadata,
852
+ timeoutMillis: this.config.timeout
853
+ };
854
+ }
855
+ return {
856
+ url,
857
+ headers,
858
+ timeoutMillis: this.config.timeout
859
+ };
860
+ }
861
+ /**
862
+ * Wait for setup to complete. If init() was called, this awaits the
863
+ * already-in-flight setup promise. If init() was never called (standalone
864
+ * usage without Mastra), this triggers setup on first use.
865
+ */
866
+ ensureSetup() {
867
+ if (!this.setupPromise) this.setupPromise = this.setupAllSignals();
868
+ return this.setupPromise;
869
+ }
870
+ /**
871
+ * Resolve provider config once and set up all enabled signals in parallel.
872
+ * Each signal setup catches its own errors — this method never rejects.
873
+ */
874
+ async setupAllSignals() {
875
+ const resolved = this.resolveProvider();
876
+ if (!resolved) {
877
+ this.debugLog("Setup skipped: provider not resolved");
878
+ return;
879
+ }
880
+ const protocol = resolved.protocol;
881
+ const setupTasks = [];
882
+ if (this.config.signals?.traces !== false) setupTasks.push(this.setupTraces(resolved, protocol));
883
+ else this.debugLog("Trace export disabled via config");
884
+ if (this.config.signals?.logs !== false) setupTasks.push(this.setupLogs(resolved, protocol));
885
+ else this.debugLog("Log export disabled via config");
886
+ await Promise.all(setupTasks);
887
+ this.debugLog(`Setup complete [traces=${!!this.processor}, logs=${!!this.otelLogger}]`);
888
+ }
889
+ async setupTraces(resolved, protocol) {
890
+ try {
891
+ if (this.config.exporter) this.exporter = this.config.exporter;
892
+ else {
893
+ const headers = resolved.headers;
894
+ const endpoint = protocol === "zipkin" ? resolved.endpoint : this.getSignalEndpoint(resolved, "traces");
895
+ this.debugLog(`Setting up trace exporter: protocol=${protocol}, endpoint=${endpoint}`);
896
+ const ExporterClass = await loadExporter(protocol, this.providerName);
897
+ if (!ExporterClass) {
898
+ this.debugLog(`Trace exporter not available for protocol: ${protocol}`);
899
+ return;
900
+ }
901
+ const exporterOptions = protocol === "zipkin" ? {
902
+ url: endpoint,
903
+ headers
904
+ } : await this.buildExporterOptions(protocol, endpoint, headers);
905
+ this.exporter = new ExporterClass(exporterOptions);
906
+ this.debugLog(`Trace exporter created: ${this.exporter?.constructor?.name ?? "unknown"} -> ${endpoint}`);
907
+ }
908
+ const serviceName = this.observabilityConfig?.serviceName || "mastra-service";
909
+ this.spanConverter = new SpanConverter({
910
+ packageName: "@mastra/otel-exporter",
911
+ serviceName,
912
+ config: this.config,
913
+ format: "GenAI_v1_38_0"
914
+ });
915
+ const exporterForProcessor = this.isDebug ? new DebugSpanExporterWrapper(this.exporter, (msg) => this.debugLog(msg)) : this.exporter;
916
+ this.processor = new _opentelemetry_sdk_trace_base.BatchSpanProcessor(exporterForProcessor, {
917
+ maxExportBatchSize: this.config.batchSize || 512,
918
+ maxQueueSize: 2048,
919
+ scheduledDelayMillis: 5e3,
920
+ exportTimeoutMillis: this.config.timeout || 3e4
921
+ });
922
+ this.debugLog(`Trace export initialized (service.name: "${serviceName}", batch size: ${this.config.batchSize || 512}, delay: 5s)`);
923
+ } catch (error) {
924
+ this.logger.warn("[OtelExporter] Failed to initialize trace export");
925
+ this.debugLog("Trace setup error:", error);
926
+ }
927
+ }
928
+ async setupLogs(resolved, protocol) {
929
+ try {
930
+ const LogExporterClass = await loadSignalExporter("logs", protocol, this.providerName);
931
+ if (!LogExporterClass) {
932
+ this.debugLog(`Log exporter package not available for protocol "${protocol}". Log export disabled.`);
933
+ return;
934
+ }
935
+ const logEndpoint = this.getSignalEndpoint(resolved, "logs");
936
+ const headers = resolved.headers;
937
+ this.debugLog(`Setting up log exporter: protocol=${protocol}, endpoint=${logEndpoint}`);
938
+ const logExporter = new LogExporterClass(await this.buildExporterOptions(protocol, logEndpoint, headers));
939
+ const exporterForProcessor = this.isDebug ? new DebugLogExporterWrapper(logExporter, (msg) => this.debugLog(msg)) : logExporter;
940
+ const resource = (0, _opentelemetry_resources.resourceFromAttributes)({
941
+ ...this.config.resourceAttributes ?? {},
942
+ [_opentelemetry_semantic_conventions.ATTR_SERVICE_NAME]: this.observabilityConfig?.serviceName || "mastra-service"
943
+ });
944
+ this.loggerProvider = new _opentelemetry_sdk_logs.LoggerProvider({
945
+ resource,
946
+ processors: [new _opentelemetry_sdk_logs.BatchLogRecordProcessor(exporterForProcessor, {
947
+ maxExportBatchSize: this.config.batchSize || 512,
948
+ maxQueueSize: 2048,
949
+ scheduledDelayMillis: 5e3,
950
+ exportTimeoutMillis: this.config.timeout || 3e4
951
+ })]
952
+ });
953
+ this.otelLogger = this.loggerProvider.getLogger("@mastra/otel-exporter");
954
+ this.debugLog(`Log export initialized (endpoint: ${logEndpoint})`);
955
+ } catch (error) {
956
+ this.logger.warn("[OtelExporter] Failed to initialize log export");
957
+ this.debugLog("Log setup error:", error);
958
+ }
959
+ }
960
+ async _exportTracingEvent(event) {
961
+ if (this.config.signals?.traces === false) return;
962
+ if (event.type !== _mastra_core_observability.TracingEventType.SPAN_ENDED) return;
963
+ await this.ensureSetup();
964
+ if (this.isDisabled || !this.processor) return;
965
+ const span = event.exportedSpan;
966
+ try {
967
+ const otelSpan = await this.spanConverter.convertSpan(span);
968
+ this.processor.onEnd(otelSpan);
969
+ this.debugLog(`Queued span ${span.id} (trace: ${span.traceId}, parent: ${span.parentSpanId || "none"}, type: ${span.type})`);
970
+ } catch (error) {
971
+ this.logger.error(`[OtelExporter] Failed to export span ${span.id}:`, error);
972
+ }
973
+ }
974
+ async onLogEvent(event) {
975
+ this.debugLog(`onLogEvent received (level: ${event.log.level}, message: "${event.log.message}")`);
976
+ await this.ensureSetup();
977
+ if (this.isDisabled) {
978
+ this.debugLog("Log event skipped: exporter is disabled");
979
+ return;
980
+ }
981
+ if (!this.otelLogger) {
982
+ this.debugLog("Log event skipped: log exporter not available");
983
+ return;
984
+ }
985
+ try {
986
+ const logParams = convertLog(event.log);
987
+ const attributes = { ...logParams.attributes };
988
+ let logContext = _opentelemetry_api.context.active();
989
+ if (logParams.traceId && logParams.spanId) {
990
+ attributes["mastra.traceId"] = logParams.traceId;
991
+ attributes["mastra.spanId"] = logParams.spanId;
992
+ logContext = _opentelemetry_api.trace.setSpanContext(logContext, {
993
+ traceId: logParams.traceId,
994
+ spanId: logParams.spanId,
995
+ traceFlags: _opentelemetry_api.TraceFlags.SAMPLED,
996
+ isRemote: false
997
+ });
998
+ }
999
+ this.otelLogger.emit({
1000
+ timestamp: logParams.timestamp,
1001
+ severityNumber: logParams.severityNumber,
1002
+ severityText: logParams.severityText,
1003
+ body: logParams.body,
1004
+ attributes,
1005
+ context: logContext
1006
+ });
1007
+ this.debugLog(`Exported log (level: ${event.log.level}, trace: ${event.log.traceId || "none"})`);
1008
+ } catch (error) {
1009
+ this.logger.error("[OtelExporter] Failed to export log:", error);
1010
+ }
1011
+ }
1012
+ /**
1013
+ * Force flush any buffered data without shutting down the exporter.
1014
+ * Delegates to all active processors/providers.
1015
+ */
1016
+ async flush() {
1017
+ if (this.setupPromise) await this.setupPromise;
1018
+ const flushPromises = [];
1019
+ const signals = [];
1020
+ if (this.processor) {
1021
+ flushPromises.push(this.processor.forceFlush());
1022
+ signals.push("traces");
1023
+ }
1024
+ if (this.loggerProvider) {
1025
+ flushPromises.push(this.loggerProvider.forceFlush());
1026
+ signals.push("logs");
1027
+ }
1028
+ if (flushPromises.length > 0) {
1029
+ this.debugLog(`Flushing signals: ${signals.join(", ")}...`);
1030
+ await Promise.all(flushPromises);
1031
+ this.debugLog("Flushed all pending data");
1032
+ } else this.debugLog("Flush called but no active exporters");
1033
+ }
1034
+ async shutdown() {
1035
+ if (this.setupPromise) await this.setupPromise;
1036
+ const shutdownPromises = [];
1037
+ if (this.processor) shutdownPromises.push(this.processor.shutdown());
1038
+ if (this.loggerProvider) shutdownPromises.push(this.loggerProvider.shutdown());
1039
+ if (shutdownPromises.length > 0) await Promise.all(shutdownPromises);
1040
+ }
1189
1041
  };
1190
-
1042
+ //#endregion
1191
1043
  exports.OtelExporter = OtelExporter;
1192
1044
  exports.SpanConverter = SpanConverter;
1193
1045
  exports.buildLogAttributes = buildLogAttributes;
@@ -1196,5 +1048,5 @@ exports.getAttributes = getAttributes;
1196
1048
  exports.getSpanKind = getSpanKind;
1197
1049
  exports.getSpanName = getSpanName;
1198
1050
  exports.mapSeverity = mapSeverity;
1199
- //# sourceMappingURL=index.cjs.map
1051
+
1200
1052
  //# sourceMappingURL=index.cjs.map