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