@mastra/otel-exporter 0.0.0-1.x-tester-20251106055847

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