@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/CHANGELOG.md +16 -0
- package/dist/index.cjs +975 -1123
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +973 -1120
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
package/dist/index.cjs
CHANGED
|
@@ -1,1193 +1,1045 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/provider-configs.ts
|
|
145
157
|
function resolveProviderConfig(config, debug) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
313
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
|
|
413
|
-
|
|
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
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
|
|
396
|
+
return name.replace(/[^\p{L}\p{N}._ -]/gu, "");
|
|
440
397
|
}
|
|
441
398
|
function getSpanIdentifier(span) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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
|
-
|
|
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
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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
|
-
|
|
554
|
+
//#endregion
|
|
555
|
+
//#region src/span-converter.ts
|
|
556
|
+
/**
|
|
557
|
+
* Convert Mastra Spans to OpenTelemetry spans
|
|
558
|
+
*/
|
|
641
559
|
var SpanConverter = class {
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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
|
-
|
|
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
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
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
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
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
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
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
|
-
|
|
1051
|
+
|
|
1200
1052
|
//# sourceMappingURL=index.cjs.map
|