@mastra/otel-exporter 0.0.0-suspendRuntimeContextTypeFix-20250930142630
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 +10 -0
- package/LICENSE.md +15 -0
- package/README.md +435 -0
- package/dist/ai-tracing.d.ts +26 -0
- package/dist/ai-tracing.d.ts.map +1 -0
- package/dist/index.cjs +754 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +752 -0
- package/dist/index.js.map +1 -0
- package/dist/loadExporter.d.ts +6 -0
- package/dist/loadExporter.d.ts.map +1 -0
- package/dist/mastra-span.d.ts +37 -0
- package/dist/mastra-span.d.ts.map +1 -0
- package/dist/provider-configs.d.ts +11 -0
- package/dist/provider-configs.d.ts.map +1 -0
- package/dist/span-converter.d.ts +38 -0
- package/dist/span-converter.d.ts.map +1 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +93 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,752 @@
|
|
|
1
|
+
import { AITracingEventType, AISpanType } from '@mastra/core/ai-tracing';
|
|
2
|
+
import { ConsoleLogger } from '@mastra/core/logger';
|
|
3
|
+
import { diag, DiagConsoleLogger, DiagLogLevel, SpanKind, SpanStatusCode, TraceFlags } from '@opentelemetry/api';
|
|
4
|
+
import { resourceFromAttributes } from '@opentelemetry/resources';
|
|
5
|
+
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
6
|
+
import { ATTR_TELEMETRY_SDK_LANGUAGE, ATTR_TELEMETRY_SDK_VERSION, ATTR_TELEMETRY_SDK_NAME, ATTR_SERVICE_VERSION, ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
|
|
7
|
+
|
|
8
|
+
// src/ai-tracing.ts
|
|
9
|
+
|
|
10
|
+
// src/loadExporter.ts
|
|
11
|
+
var OTLPHttpExporter;
|
|
12
|
+
var OTLPGrpcExporter;
|
|
13
|
+
var OTLPProtoExporter;
|
|
14
|
+
var ZipkinExporter;
|
|
15
|
+
async function loadExporter(protocol, provider) {
|
|
16
|
+
switch (protocol) {
|
|
17
|
+
case "zipkin":
|
|
18
|
+
if (!ZipkinExporter) {
|
|
19
|
+
try {
|
|
20
|
+
const module = await import('@opentelemetry/exporter-zipkin');
|
|
21
|
+
ZipkinExporter = module.ZipkinExporter;
|
|
22
|
+
} catch {
|
|
23
|
+
console.error(
|
|
24
|
+
`[OtelExporter] Zipkin exporter is not installed.
|
|
25
|
+
To use Zipkin export, install the required package:
|
|
26
|
+
npm install @opentelemetry/exporter-zipkin`
|
|
27
|
+
);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return ZipkinExporter;
|
|
32
|
+
case "grpc":
|
|
33
|
+
if (!OTLPGrpcExporter) {
|
|
34
|
+
try {
|
|
35
|
+
const module = await import('@opentelemetry/exporter-trace-otlp-grpc');
|
|
36
|
+
OTLPGrpcExporter = module.OTLPTraceExporter;
|
|
37
|
+
} catch {
|
|
38
|
+
const providerInfo = provider ? ` (required for ${provider})` : "";
|
|
39
|
+
console.error(
|
|
40
|
+
`[OtelExporter] gRPC exporter is not installed${providerInfo}.
|
|
41
|
+
To use gRPC export, install the required packages:
|
|
42
|
+
npm install @opentelemetry/exporter-trace-otlp-grpc @grpc/grpc-js`
|
|
43
|
+
);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return OTLPGrpcExporter;
|
|
48
|
+
case "http/protobuf":
|
|
49
|
+
if (!OTLPProtoExporter) {
|
|
50
|
+
try {
|
|
51
|
+
const module = await import('@opentelemetry/exporter-trace-otlp-proto');
|
|
52
|
+
OTLPProtoExporter = module.OTLPTraceExporter;
|
|
53
|
+
} catch {
|
|
54
|
+
const providerInfo = provider ? ` (required for ${provider})` : "";
|
|
55
|
+
console.error(
|
|
56
|
+
`[OtelExporter] HTTP/Protobuf exporter is not installed${providerInfo}.
|
|
57
|
+
To use HTTP/Protobuf export, install the required package:
|
|
58
|
+
npm install @opentelemetry/exporter-trace-otlp-proto`
|
|
59
|
+
);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return OTLPProtoExporter;
|
|
64
|
+
case "http/json":
|
|
65
|
+
default:
|
|
66
|
+
if (!OTLPHttpExporter) {
|
|
67
|
+
try {
|
|
68
|
+
const module = await import('@opentelemetry/exporter-trace-otlp-http');
|
|
69
|
+
OTLPHttpExporter = module.OTLPTraceExporter;
|
|
70
|
+
} catch {
|
|
71
|
+
const providerInfo = provider ? ` (required for ${provider})` : "";
|
|
72
|
+
console.error(
|
|
73
|
+
`[OtelExporter] HTTP/JSON exporter is not installed${providerInfo}.
|
|
74
|
+
To use HTTP/JSON export, install the required package:
|
|
75
|
+
npm install @opentelemetry/exporter-trace-otlp-http`
|
|
76
|
+
);
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return OTLPHttpExporter;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/provider-configs.ts
|
|
85
|
+
function resolveProviderConfig(config) {
|
|
86
|
+
if ("dash0" in config) {
|
|
87
|
+
return resolveDash0Config(config.dash0);
|
|
88
|
+
} else if ("signoz" in config) {
|
|
89
|
+
return resolveSignozConfig(config.signoz);
|
|
90
|
+
} else if ("newrelic" in config) {
|
|
91
|
+
return resolveNewRelicConfig(config.newrelic);
|
|
92
|
+
} else if ("traceloop" in config) {
|
|
93
|
+
return resolveTraceloopConfig(config.traceloop);
|
|
94
|
+
} else if ("laminar" in config) {
|
|
95
|
+
return resolveLaminarConfig(config.laminar);
|
|
96
|
+
} else if ("custom" in config) {
|
|
97
|
+
return resolveCustomConfig(config.custom);
|
|
98
|
+
} else {
|
|
99
|
+
const _exhaustive = config;
|
|
100
|
+
return _exhaustive;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function resolveDash0Config(config) {
|
|
104
|
+
if (!config.apiKey) {
|
|
105
|
+
console.error("[OtelExporter] Dash0 configuration requires apiKey. Tracing will be disabled.");
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
if (!config.endpoint) {
|
|
109
|
+
console.error("[OtelExporter] Dash0 configuration requires endpoint. Tracing will be disabled.");
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
let endpoint = config.endpoint;
|
|
113
|
+
if (!endpoint.includes("/v1/traces")) {
|
|
114
|
+
endpoint = `${endpoint}/v1/traces`;
|
|
115
|
+
}
|
|
116
|
+
const headers = {
|
|
117
|
+
authorization: `Bearer ${config.apiKey}`
|
|
118
|
+
// lowercase for gRPC metadata
|
|
119
|
+
};
|
|
120
|
+
if (config.dataset) {
|
|
121
|
+
headers["dash0-dataset"] = config.dataset;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
endpoint,
|
|
125
|
+
headers,
|
|
126
|
+
protocol: "grpc"
|
|
127
|
+
// Use gRPC for Dash0
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function resolveSignozConfig(config) {
|
|
131
|
+
if (!config.apiKey) {
|
|
132
|
+
console.error("[OtelExporter] SigNoz configuration requires apiKey. Tracing will be disabled.");
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const endpoint = config.endpoint || `https://ingest.${config.region || "us"}.signoz.cloud:443/v1/traces`;
|
|
136
|
+
return {
|
|
137
|
+
endpoint,
|
|
138
|
+
headers: {
|
|
139
|
+
"signoz-ingestion-key": config.apiKey
|
|
140
|
+
},
|
|
141
|
+
protocol: "http/protobuf"
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function resolveNewRelicConfig(config) {
|
|
145
|
+
if (!config.apiKey) {
|
|
146
|
+
console.error("[OtelExporter] New Relic configuration requires apiKey (license key). Tracing will be disabled.");
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const endpoint = config.endpoint || "https://otlp.nr-data.net:443/v1/traces";
|
|
150
|
+
return {
|
|
151
|
+
endpoint,
|
|
152
|
+
headers: {
|
|
153
|
+
"api-key": config.apiKey
|
|
154
|
+
},
|
|
155
|
+
protocol: "http/protobuf"
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function resolveTraceloopConfig(config) {
|
|
159
|
+
if (!config.apiKey) {
|
|
160
|
+
console.error("[OtelExporter] Traceloop configuration requires apiKey. Tracing will be disabled.");
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
const endpoint = config.endpoint || "https://api.traceloop.com/v1/traces";
|
|
164
|
+
const headers = {
|
|
165
|
+
Authorization: `Bearer ${config.apiKey}`
|
|
166
|
+
};
|
|
167
|
+
if (config.destinationId) {
|
|
168
|
+
headers["x-traceloop-destination-id"] = config.destinationId;
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
endpoint,
|
|
172
|
+
headers,
|
|
173
|
+
protocol: "http/json"
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function resolveLaminarConfig(config) {
|
|
177
|
+
if (!config.apiKey) {
|
|
178
|
+
console.error("[OtelExporter] Laminar configuration requires apiKey. Tracing will be disabled.");
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
const endpoint = config.endpoint || "https://api.lmnr.ai/v1/traces";
|
|
182
|
+
const headers = {
|
|
183
|
+
Authorization: `Bearer ${config.apiKey}`
|
|
184
|
+
};
|
|
185
|
+
if (config.teamId) {
|
|
186
|
+
headers["x-laminar-team-id"] = config.teamId;
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
endpoint,
|
|
190
|
+
headers,
|
|
191
|
+
protocol: "http/protobuf"
|
|
192
|
+
// Use HTTP/protobuf instead of gRPC for better compatibility
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function resolveCustomConfig(config) {
|
|
196
|
+
if (!config.endpoint) {
|
|
197
|
+
console.error("[OtelExporter] Custom configuration requires endpoint. Tracing will be disabled.");
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
endpoint: config.endpoint,
|
|
202
|
+
headers: config.headers || {},
|
|
203
|
+
protocol: config.protocol || "http/json"
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
var MastraReadableSpan = class {
|
|
207
|
+
name;
|
|
208
|
+
kind;
|
|
209
|
+
spanContext;
|
|
210
|
+
parentSpanId;
|
|
211
|
+
startTime;
|
|
212
|
+
endTime;
|
|
213
|
+
status;
|
|
214
|
+
attributes;
|
|
215
|
+
links;
|
|
216
|
+
events;
|
|
217
|
+
duration;
|
|
218
|
+
ended;
|
|
219
|
+
resource;
|
|
220
|
+
instrumentationLibrary;
|
|
221
|
+
instrumentationScope;
|
|
222
|
+
droppedAttributesCount = 0;
|
|
223
|
+
droppedEventsCount = 0;
|
|
224
|
+
droppedLinksCount = 0;
|
|
225
|
+
constructor(aiSpan, attributes, kind, parentSpanId, resource, instrumentationLibrary) {
|
|
226
|
+
this.name = aiSpan.name;
|
|
227
|
+
this.kind = kind;
|
|
228
|
+
this.attributes = attributes;
|
|
229
|
+
this.parentSpanId = parentSpanId;
|
|
230
|
+
this.links = [];
|
|
231
|
+
this.events = [];
|
|
232
|
+
this.startTime = this.dateToHrTime(aiSpan.startTime);
|
|
233
|
+
this.endTime = aiSpan.endTime ? this.dateToHrTime(aiSpan.endTime) : this.startTime;
|
|
234
|
+
this.ended = !!aiSpan.endTime;
|
|
235
|
+
if (aiSpan.endTime) {
|
|
236
|
+
const durationMs = aiSpan.endTime.getTime() - aiSpan.startTime.getTime();
|
|
237
|
+
this.duration = [Math.floor(durationMs / 1e3), durationMs % 1e3 * 1e6];
|
|
238
|
+
} else {
|
|
239
|
+
this.duration = [0, 0];
|
|
240
|
+
}
|
|
241
|
+
if (aiSpan.errorInfo) {
|
|
242
|
+
this.status = {
|
|
243
|
+
code: SpanStatusCode.ERROR,
|
|
244
|
+
message: aiSpan.errorInfo.message
|
|
245
|
+
};
|
|
246
|
+
this.events.push({
|
|
247
|
+
name: "exception",
|
|
248
|
+
attributes: {
|
|
249
|
+
"exception.message": aiSpan.errorInfo.message,
|
|
250
|
+
"exception.type": "Error",
|
|
251
|
+
...aiSpan.errorInfo.details?.stack && {
|
|
252
|
+
"exception.stacktrace": aiSpan.errorInfo.details.stack
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
time: this.startTime,
|
|
256
|
+
droppedAttributesCount: 0
|
|
257
|
+
});
|
|
258
|
+
} else if (aiSpan.endTime) {
|
|
259
|
+
this.status = { code: SpanStatusCode.OK };
|
|
260
|
+
} else {
|
|
261
|
+
this.status = { code: SpanStatusCode.UNSET };
|
|
262
|
+
}
|
|
263
|
+
if (aiSpan.isEvent) {
|
|
264
|
+
this.events.push({
|
|
265
|
+
name: "instant_event",
|
|
266
|
+
attributes: {},
|
|
267
|
+
time: this.startTime,
|
|
268
|
+
droppedAttributesCount: 0
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
this.spanContext = () => ({
|
|
272
|
+
traceId: aiSpan.traceId,
|
|
273
|
+
spanId: aiSpan.id,
|
|
274
|
+
traceFlags: TraceFlags.SAMPLED,
|
|
275
|
+
isRemote: false
|
|
276
|
+
});
|
|
277
|
+
this.resource = resource || {};
|
|
278
|
+
this.instrumentationLibrary = instrumentationLibrary || {
|
|
279
|
+
name: "@mastra/otel",
|
|
280
|
+
version: "1.0.0"
|
|
281
|
+
};
|
|
282
|
+
this.instrumentationScope = this.instrumentationLibrary;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Convert JavaScript Date to hrtime format
|
|
286
|
+
*/
|
|
287
|
+
dateToHrTime(date) {
|
|
288
|
+
const ms = date.getTime();
|
|
289
|
+
const seconds = Math.floor(ms / 1e3);
|
|
290
|
+
const nanoseconds = ms % 1e3 * 1e6;
|
|
291
|
+
return [seconds, nanoseconds];
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
// src/span-converter.ts
|
|
296
|
+
var SPAN_KIND_MAPPING = {
|
|
297
|
+
// LLM operations are CLIENT spans (calling external AI services)
|
|
298
|
+
[AISpanType.LLM_GENERATION]: SpanKind.CLIENT,
|
|
299
|
+
[AISpanType.LLM_CHUNK]: SpanKind.CLIENT,
|
|
300
|
+
// Tool calls can be CLIENT (external) or INTERNAL based on context
|
|
301
|
+
[AISpanType.TOOL_CALL]: SpanKind.INTERNAL,
|
|
302
|
+
[AISpanType.MCP_TOOL_CALL]: SpanKind.CLIENT,
|
|
303
|
+
// Root spans for agent/workflow are SERVER (entry points)
|
|
304
|
+
[AISpanType.AGENT_RUN]: SpanKind.SERVER,
|
|
305
|
+
[AISpanType.WORKFLOW_RUN]: SpanKind.SERVER,
|
|
306
|
+
// Internal workflow operations
|
|
307
|
+
[AISpanType.WORKFLOW_STEP]: SpanKind.INTERNAL,
|
|
308
|
+
[AISpanType.WORKFLOW_LOOP]: SpanKind.INTERNAL,
|
|
309
|
+
[AISpanType.WORKFLOW_PARALLEL]: SpanKind.INTERNAL,
|
|
310
|
+
[AISpanType.WORKFLOW_CONDITIONAL]: SpanKind.INTERNAL,
|
|
311
|
+
[AISpanType.WORKFLOW_CONDITIONAL_EVAL]: SpanKind.INTERNAL,
|
|
312
|
+
[AISpanType.WORKFLOW_SLEEP]: SpanKind.INTERNAL,
|
|
313
|
+
[AISpanType.WORKFLOW_WAIT_EVENT]: SpanKind.INTERNAL,
|
|
314
|
+
[AISpanType.GENERIC]: SpanKind.INTERNAL
|
|
315
|
+
};
|
|
316
|
+
var SpanConverter = class {
|
|
317
|
+
resource;
|
|
318
|
+
instrumentationLibrary;
|
|
319
|
+
constructor(resource) {
|
|
320
|
+
this.resource = resource;
|
|
321
|
+
this.instrumentationLibrary = {
|
|
322
|
+
name: "@mastra/otel",
|
|
323
|
+
version: "1.0.0"
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Convert a Mastra AI span to an OpenTelemetry ReadableSpan
|
|
328
|
+
* This preserves Mastra's trace and span IDs
|
|
329
|
+
*/
|
|
330
|
+
convertSpan(aiSpan) {
|
|
331
|
+
const spanKind = this.getSpanKind(aiSpan);
|
|
332
|
+
const attributes = this.buildAttributes(aiSpan);
|
|
333
|
+
const spanName = this.buildSpanName(aiSpan);
|
|
334
|
+
const otelSpan = { ...aiSpan, name: spanName };
|
|
335
|
+
return new MastraReadableSpan(
|
|
336
|
+
otelSpan,
|
|
337
|
+
attributes,
|
|
338
|
+
spanKind,
|
|
339
|
+
aiSpan.parentSpanId,
|
|
340
|
+
// Use the parentSpanId from the Mastra span directly
|
|
341
|
+
this.resource,
|
|
342
|
+
this.instrumentationLibrary
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Get the appropriate SpanKind based on span type and context
|
|
347
|
+
*/
|
|
348
|
+
getSpanKind(aiSpan) {
|
|
349
|
+
if (aiSpan.isRootSpan) {
|
|
350
|
+
if (aiSpan.type === AISpanType.AGENT_RUN || aiSpan.type === AISpanType.WORKFLOW_RUN) {
|
|
351
|
+
return SpanKind.SERVER;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return SPAN_KIND_MAPPING[aiSpan.type] || SpanKind.INTERNAL;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Build OTEL-compliant span name based on span type and attributes
|
|
358
|
+
*/
|
|
359
|
+
buildSpanName(aiSpan) {
|
|
360
|
+
switch (aiSpan.type) {
|
|
361
|
+
case AISpanType.LLM_GENERATION: {
|
|
362
|
+
const attrs = aiSpan.attributes;
|
|
363
|
+
const operation = attrs?.resultType === "tool_selection" ? "tool_selection" : "chat";
|
|
364
|
+
const model = attrs?.model || "unknown";
|
|
365
|
+
return `${operation} ${model}`;
|
|
366
|
+
}
|
|
367
|
+
case AISpanType.TOOL_CALL:
|
|
368
|
+
case AISpanType.MCP_TOOL_CALL: {
|
|
369
|
+
const toolAttrs = aiSpan.attributes;
|
|
370
|
+
const toolName = toolAttrs?.toolId || "unknown";
|
|
371
|
+
return `tool.execute ${toolName}`;
|
|
372
|
+
}
|
|
373
|
+
case AISpanType.AGENT_RUN: {
|
|
374
|
+
const agentAttrs = aiSpan.attributes;
|
|
375
|
+
const agentId = agentAttrs?.agentId || "unknown";
|
|
376
|
+
return `agent.${agentId}`;
|
|
377
|
+
}
|
|
378
|
+
case AISpanType.WORKFLOW_RUN: {
|
|
379
|
+
const workflowAttrs = aiSpan.attributes;
|
|
380
|
+
const workflowId = workflowAttrs?.workflowId || "unknown";
|
|
381
|
+
return `workflow.${workflowId}`;
|
|
382
|
+
}
|
|
383
|
+
case AISpanType.WORKFLOW_STEP:
|
|
384
|
+
return aiSpan.name;
|
|
385
|
+
default:
|
|
386
|
+
return aiSpan.name;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Build OpenTelemetry attributes from Mastra AI span
|
|
391
|
+
* Following OTEL Semantic Conventions for GenAI
|
|
392
|
+
*/
|
|
393
|
+
buildAttributes(aiSpan) {
|
|
394
|
+
const attributes = {};
|
|
395
|
+
attributes["gen_ai.operation.name"] = this.getOperationName(aiSpan);
|
|
396
|
+
attributes["span.kind"] = this.getSpanKindString(aiSpan);
|
|
397
|
+
attributes["mastra.span.type"] = aiSpan.type;
|
|
398
|
+
attributes["mastra.trace_id"] = aiSpan.traceId;
|
|
399
|
+
attributes["mastra.span_id"] = aiSpan.id;
|
|
400
|
+
if (aiSpan.parentSpanId) {
|
|
401
|
+
attributes["mastra.parent_span_id"] = aiSpan.parentSpanId;
|
|
402
|
+
}
|
|
403
|
+
if (aiSpan.input !== void 0) {
|
|
404
|
+
const inputStr = typeof aiSpan.input === "string" ? aiSpan.input : JSON.stringify(aiSpan.input);
|
|
405
|
+
attributes["input"] = inputStr;
|
|
406
|
+
if (aiSpan.type === AISpanType.LLM_GENERATION) {
|
|
407
|
+
attributes["gen_ai.prompt"] = inputStr;
|
|
408
|
+
} else if (aiSpan.type === AISpanType.TOOL_CALL || aiSpan.type === AISpanType.MCP_TOOL_CALL) {
|
|
409
|
+
attributes["gen_ai.tool.input"] = inputStr;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (aiSpan.output !== void 0) {
|
|
413
|
+
const outputStr = typeof aiSpan.output === "string" ? aiSpan.output : JSON.stringify(aiSpan.output);
|
|
414
|
+
attributes["output"] = outputStr;
|
|
415
|
+
if (aiSpan.type === AISpanType.LLM_GENERATION) {
|
|
416
|
+
attributes["gen_ai.completion"] = outputStr;
|
|
417
|
+
} else if (aiSpan.type === AISpanType.TOOL_CALL || aiSpan.type === AISpanType.MCP_TOOL_CALL) {
|
|
418
|
+
attributes["gen_ai.tool.output"] = outputStr;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (aiSpan.type === AISpanType.LLM_GENERATION && aiSpan.attributes) {
|
|
422
|
+
const llmAttrs = aiSpan.attributes;
|
|
423
|
+
if (llmAttrs.model) {
|
|
424
|
+
attributes["gen_ai.request.model"] = llmAttrs.model;
|
|
425
|
+
}
|
|
426
|
+
if (llmAttrs.provider) {
|
|
427
|
+
attributes["gen_ai.system"] = llmAttrs.provider;
|
|
428
|
+
}
|
|
429
|
+
if (llmAttrs.usage) {
|
|
430
|
+
const inputTokens = llmAttrs.usage.inputTokens ?? llmAttrs.usage.promptTokens;
|
|
431
|
+
const outputTokens = llmAttrs.usage.outputTokens ?? llmAttrs.usage.completionTokens;
|
|
432
|
+
if (inputTokens !== void 0) {
|
|
433
|
+
attributes["gen_ai.usage.input_tokens"] = inputTokens;
|
|
434
|
+
}
|
|
435
|
+
if (outputTokens !== void 0) {
|
|
436
|
+
attributes["gen_ai.usage.output_tokens"] = outputTokens;
|
|
437
|
+
}
|
|
438
|
+
if (llmAttrs.usage.totalTokens !== void 0) {
|
|
439
|
+
attributes["gen_ai.usage.total_tokens"] = llmAttrs.usage.totalTokens;
|
|
440
|
+
}
|
|
441
|
+
if (llmAttrs.usage.reasoningTokens !== void 0) {
|
|
442
|
+
attributes["gen_ai.usage.reasoning_tokens"] = llmAttrs.usage.reasoningTokens;
|
|
443
|
+
}
|
|
444
|
+
if (llmAttrs.usage.cachedInputTokens !== void 0) {
|
|
445
|
+
attributes["gen_ai.usage.cached_input_tokens"] = llmAttrs.usage.cachedInputTokens;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (llmAttrs.parameters) {
|
|
449
|
+
if (llmAttrs.parameters.temperature !== void 0) {
|
|
450
|
+
attributes["gen_ai.request.temperature"] = llmAttrs.parameters.temperature;
|
|
451
|
+
}
|
|
452
|
+
if (llmAttrs.parameters.maxOutputTokens !== void 0) {
|
|
453
|
+
attributes["gen_ai.request.max_tokens"] = llmAttrs.parameters.maxOutputTokens;
|
|
454
|
+
}
|
|
455
|
+
if (llmAttrs.parameters.topP !== void 0) {
|
|
456
|
+
attributes["gen_ai.request.top_p"] = llmAttrs.parameters.topP;
|
|
457
|
+
}
|
|
458
|
+
if (llmAttrs.parameters.topK !== void 0) {
|
|
459
|
+
attributes["gen_ai.request.top_k"] = llmAttrs.parameters.topK;
|
|
460
|
+
}
|
|
461
|
+
if (llmAttrs.parameters.presencePenalty !== void 0) {
|
|
462
|
+
attributes["gen_ai.request.presence_penalty"] = llmAttrs.parameters.presencePenalty;
|
|
463
|
+
}
|
|
464
|
+
if (llmAttrs.parameters.frequencyPenalty !== void 0) {
|
|
465
|
+
attributes["gen_ai.request.frequency_penalty"] = llmAttrs.parameters.frequencyPenalty;
|
|
466
|
+
}
|
|
467
|
+
if (llmAttrs.parameters.stopSequences) {
|
|
468
|
+
attributes["gen_ai.request.stop_sequences"] = JSON.stringify(llmAttrs.parameters.stopSequences);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (llmAttrs.finishReason) {
|
|
472
|
+
attributes["gen_ai.response.finish_reasons"] = llmAttrs.finishReason;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if ((aiSpan.type === AISpanType.TOOL_CALL || aiSpan.type === AISpanType.MCP_TOOL_CALL) && aiSpan.attributes) {
|
|
476
|
+
const toolAttrs = aiSpan.attributes;
|
|
477
|
+
if (toolAttrs.toolId) {
|
|
478
|
+
attributes["gen_ai.tool.name"] = toolAttrs.toolId;
|
|
479
|
+
}
|
|
480
|
+
if (aiSpan.type === AISpanType.MCP_TOOL_CALL) {
|
|
481
|
+
const mcpAttrs = toolAttrs;
|
|
482
|
+
if (mcpAttrs.mcpServer) {
|
|
483
|
+
attributes["mcp.server"] = mcpAttrs.mcpServer;
|
|
484
|
+
}
|
|
485
|
+
if (mcpAttrs.serverVersion) {
|
|
486
|
+
attributes["mcp.server.version"] = mcpAttrs.serverVersion;
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
if (toolAttrs.toolDescription) {
|
|
490
|
+
attributes["gen_ai.tool.description"] = toolAttrs.toolDescription;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (toolAttrs.success !== void 0) {
|
|
494
|
+
attributes["gen_ai.tool.success"] = toolAttrs.success;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (aiSpan.type === AISpanType.AGENT_RUN && aiSpan.attributes) {
|
|
498
|
+
const agentAttrs = aiSpan.attributes;
|
|
499
|
+
if (agentAttrs.agentId) {
|
|
500
|
+
attributes["agent.id"] = agentAttrs.agentId;
|
|
501
|
+
}
|
|
502
|
+
if (agentAttrs.maxSteps) {
|
|
503
|
+
attributes["agent.max_steps"] = agentAttrs.maxSteps;
|
|
504
|
+
}
|
|
505
|
+
if (agentAttrs.availableTools) {
|
|
506
|
+
attributes["agent.available_tools"] = JSON.stringify(agentAttrs.availableTools);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (aiSpan.type === AISpanType.WORKFLOW_RUN && aiSpan.attributes) {
|
|
510
|
+
const workflowAttrs = aiSpan.attributes;
|
|
511
|
+
if (workflowAttrs.workflowId) {
|
|
512
|
+
attributes["workflow.id"] = workflowAttrs.workflowId;
|
|
513
|
+
}
|
|
514
|
+
if (workflowAttrs.status) {
|
|
515
|
+
attributes["workflow.status"] = workflowAttrs.status;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (aiSpan.errorInfo) {
|
|
519
|
+
attributes["error"] = true;
|
|
520
|
+
attributes["error.type"] = aiSpan.errorInfo.id || "unknown";
|
|
521
|
+
attributes["error.message"] = aiSpan.errorInfo.message;
|
|
522
|
+
if (aiSpan.errorInfo.domain) {
|
|
523
|
+
attributes["error.domain"] = aiSpan.errorInfo.domain;
|
|
524
|
+
}
|
|
525
|
+
if (aiSpan.errorInfo.category) {
|
|
526
|
+
attributes["error.category"] = aiSpan.errorInfo.category;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (aiSpan.metadata) {
|
|
530
|
+
Object.entries(aiSpan.metadata).forEach(([key, value]) => {
|
|
531
|
+
if (!attributes[key]) {
|
|
532
|
+
if (value === null || value === void 0) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (typeof value === "object") {
|
|
536
|
+
attributes[key] = JSON.stringify(value);
|
|
537
|
+
} else {
|
|
538
|
+
attributes[key] = value;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
if (aiSpan.startTime) {
|
|
544
|
+
attributes["mastra.start_time"] = aiSpan.startTime.toISOString();
|
|
545
|
+
}
|
|
546
|
+
if (aiSpan.endTime) {
|
|
547
|
+
attributes["mastra.end_time"] = aiSpan.endTime.toISOString();
|
|
548
|
+
const duration = aiSpan.endTime.getTime() - aiSpan.startTime.getTime();
|
|
549
|
+
attributes["mastra.duration_ms"] = duration;
|
|
550
|
+
}
|
|
551
|
+
return attributes;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Get the operation name based on span type for gen_ai.operation.name
|
|
555
|
+
*/
|
|
556
|
+
getOperationName(aiSpan) {
|
|
557
|
+
switch (aiSpan.type) {
|
|
558
|
+
case AISpanType.LLM_GENERATION: {
|
|
559
|
+
const attrs = aiSpan.attributes;
|
|
560
|
+
return attrs?.resultType === "tool_selection" ? "tool_selection" : "chat";
|
|
561
|
+
}
|
|
562
|
+
case AISpanType.TOOL_CALL:
|
|
563
|
+
case AISpanType.MCP_TOOL_CALL:
|
|
564
|
+
return "tool.execute";
|
|
565
|
+
case AISpanType.AGENT_RUN:
|
|
566
|
+
return "agent.run";
|
|
567
|
+
case AISpanType.WORKFLOW_RUN:
|
|
568
|
+
return "workflow.run";
|
|
569
|
+
default:
|
|
570
|
+
return aiSpan.type.replace(/_/g, ".");
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Get span kind as string for attribute
|
|
575
|
+
*/
|
|
576
|
+
getSpanKindString(aiSpan) {
|
|
577
|
+
const kind = this.getSpanKind(aiSpan);
|
|
578
|
+
switch (kind) {
|
|
579
|
+
case SpanKind.SERVER:
|
|
580
|
+
return "server";
|
|
581
|
+
case SpanKind.CLIENT:
|
|
582
|
+
return "client";
|
|
583
|
+
case SpanKind.INTERNAL:
|
|
584
|
+
return "internal";
|
|
585
|
+
case SpanKind.PRODUCER:
|
|
586
|
+
return "producer";
|
|
587
|
+
case SpanKind.CONSUMER:
|
|
588
|
+
return "consumer";
|
|
589
|
+
default:
|
|
590
|
+
return "internal";
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
// src/ai-tracing.ts
|
|
596
|
+
var OtelExporter = class {
|
|
597
|
+
config;
|
|
598
|
+
tracingConfig;
|
|
599
|
+
spanConverter;
|
|
600
|
+
processor;
|
|
601
|
+
exporter;
|
|
602
|
+
isSetup = false;
|
|
603
|
+
isDisabled = false;
|
|
604
|
+
logger;
|
|
605
|
+
name = "opentelemetry";
|
|
606
|
+
constructor(config) {
|
|
607
|
+
this.config = config;
|
|
608
|
+
this.spanConverter = new SpanConverter();
|
|
609
|
+
this.logger = new ConsoleLogger({ level: config.logLevel ?? "warn" });
|
|
610
|
+
if (config.logLevel === "debug") {
|
|
611
|
+
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Initialize with tracing configuration
|
|
616
|
+
*/
|
|
617
|
+
init(config) {
|
|
618
|
+
this.tracingConfig = config;
|
|
619
|
+
}
|
|
620
|
+
async setupExporter() {
|
|
621
|
+
if (this.isSetup) return;
|
|
622
|
+
if (!this.config.provider) {
|
|
623
|
+
this.logger.error(
|
|
624
|
+
'[OtelExporter] Provider configuration is required. Use the "custom" provider for generic endpoints.'
|
|
625
|
+
);
|
|
626
|
+
this.isDisabled = true;
|
|
627
|
+
this.isSetup = true;
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
const resolved = resolveProviderConfig(this.config.provider);
|
|
631
|
+
if (!resolved) {
|
|
632
|
+
this.isDisabled = true;
|
|
633
|
+
this.isSetup = true;
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
const endpoint = resolved.endpoint;
|
|
637
|
+
const headers = resolved.headers;
|
|
638
|
+
const protocol = resolved.protocol;
|
|
639
|
+
const providerName = Object.keys(this.config.provider)[0];
|
|
640
|
+
const ExporterClass = await loadExporter(protocol, providerName);
|
|
641
|
+
if (!ExporterClass) {
|
|
642
|
+
this.isDisabled = true;
|
|
643
|
+
this.isSetup = true;
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
try {
|
|
647
|
+
if (protocol === "zipkin") {
|
|
648
|
+
this.exporter = new ExporterClass({
|
|
649
|
+
url: endpoint,
|
|
650
|
+
headers
|
|
651
|
+
});
|
|
652
|
+
} else if (protocol === "grpc") {
|
|
653
|
+
let metadata;
|
|
654
|
+
try {
|
|
655
|
+
const grpcModule = await import('@grpc/grpc-js');
|
|
656
|
+
metadata = new grpcModule.Metadata();
|
|
657
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
658
|
+
metadata.set(key, value);
|
|
659
|
+
});
|
|
660
|
+
} catch (grpcError) {
|
|
661
|
+
this.logger.error(
|
|
662
|
+
`[OtelExporter] Failed to load gRPC metadata. Install required packages:
|
|
663
|
+
npm install @opentelemetry/exporter-trace-otlp-grpc @grpc/grpc-js
|
|
664
|
+
`,
|
|
665
|
+
grpcError
|
|
666
|
+
);
|
|
667
|
+
this.isDisabled = true;
|
|
668
|
+
this.isSetup = true;
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
this.exporter = new ExporterClass({
|
|
672
|
+
url: endpoint,
|
|
673
|
+
metadata,
|
|
674
|
+
timeoutMillis: this.config.timeout
|
|
675
|
+
});
|
|
676
|
+
} else {
|
|
677
|
+
this.exporter = new ExporterClass({
|
|
678
|
+
url: endpoint,
|
|
679
|
+
headers,
|
|
680
|
+
timeoutMillis: this.config.timeout
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
} catch (error) {
|
|
684
|
+
this.logger.error(`[OtelExporter] Failed to create exporter:`, error);
|
|
685
|
+
this.isDisabled = true;
|
|
686
|
+
this.isSetup = true;
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const resource = resourceFromAttributes({
|
|
690
|
+
[ATTR_SERVICE_NAME]: this.tracingConfig?.serviceName || "mastra-service",
|
|
691
|
+
[ATTR_SERVICE_VERSION]: "1.0.0",
|
|
692
|
+
// Add telemetry SDK information
|
|
693
|
+
[ATTR_TELEMETRY_SDK_NAME]: "@mastra/otel-exporter",
|
|
694
|
+
[ATTR_TELEMETRY_SDK_VERSION]: "1.0.0",
|
|
695
|
+
[ATTR_TELEMETRY_SDK_LANGUAGE]: "nodejs"
|
|
696
|
+
});
|
|
697
|
+
this.spanConverter = new SpanConverter(resource);
|
|
698
|
+
this.processor = new BatchSpanProcessor(this.exporter, {
|
|
699
|
+
maxExportBatchSize: this.config.batchSize || 512,
|
|
700
|
+
// Default batch size
|
|
701
|
+
maxQueueSize: 2048,
|
|
702
|
+
// Maximum spans to queue
|
|
703
|
+
scheduledDelayMillis: 5e3,
|
|
704
|
+
// Export every 5 seconds
|
|
705
|
+
exportTimeoutMillis: this.config.timeout || 3e4
|
|
706
|
+
// Export timeout
|
|
707
|
+
});
|
|
708
|
+
this.logger.debug(
|
|
709
|
+
`[OtelExporter] Using BatchSpanProcessor (batch size: ${this.config.batchSize || 512}, delay: 5s)`
|
|
710
|
+
);
|
|
711
|
+
this.isSetup = true;
|
|
712
|
+
}
|
|
713
|
+
async exportEvent(event) {
|
|
714
|
+
if (this.isDisabled) {
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
if (event.type !== AITracingEventType.SPAN_ENDED) {
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
const span = event.exportedSpan;
|
|
721
|
+
await this.exportSpan(span);
|
|
722
|
+
}
|
|
723
|
+
async exportSpan(span) {
|
|
724
|
+
if (!this.isSetup) {
|
|
725
|
+
await this.setupExporter();
|
|
726
|
+
}
|
|
727
|
+
if (this.isDisabled || !this.processor) {
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
try {
|
|
731
|
+
const readableSpan = this.spanConverter.convertSpan(span);
|
|
732
|
+
await new Promise((resolve) => {
|
|
733
|
+
this.processor.onEnd(readableSpan);
|
|
734
|
+
resolve();
|
|
735
|
+
});
|
|
736
|
+
this.logger.debug(
|
|
737
|
+
`[OtelExporter] Exported span ${span.id} (trace: ${span.traceId}, parent: ${span.parentSpanId || "none"}, type: ${span.type})`
|
|
738
|
+
);
|
|
739
|
+
} catch (error) {
|
|
740
|
+
this.logger.error(`[OtelExporter] Failed to export span ${span.id}:`, error);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
async shutdown() {
|
|
744
|
+
if (this.processor) {
|
|
745
|
+
await this.processor.shutdown();
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
export { OtelExporter };
|
|
751
|
+
//# sourceMappingURL=index.js.map
|
|
752
|
+
//# sourceMappingURL=index.js.map
|