@mastra/otel-exporter 0.3.3 → 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
- import { BaseExporter, AITracingEventType, AISpanType } from '@mastra/core/ai-tracing';
1
+ import { TracingEventType, SpanType } from '@mastra/core/observability';
2
+ import { BaseExporter } from '@mastra/observability';
2
3
  import { diag, DiagConsoleLogger, DiagLogLevel, SpanKind, SpanStatusCode, TraceFlags } from '@opentelemetry/api';
3
4
  import { resourceFromAttributes } from '@opentelemetry/resources';
4
5
  import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
5
6
  import { ATTR_TELEMETRY_SDK_LANGUAGE, ATTR_TELEMETRY_SDK_VERSION, ATTR_TELEMETRY_SDK_NAME, ATTR_SERVICE_VERSION, ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
6
7
 
7
- // src/ai-tracing.ts
8
+ // src/tracing.ts
8
9
 
9
10
  // src/loadExporter.ts
10
11
  var OTLPHttpExporter;
@@ -222,45 +223,45 @@ var MastraReadableSpan = class {
222
223
  droppedAttributesCount = 0;
223
224
  droppedEventsCount = 0;
224
225
  droppedLinksCount = 0;
225
- constructor(aiSpan, attributes, kind, parentSpanId, resource, instrumentationLibrary) {
226
- this.name = aiSpan.name;
226
+ constructor(span, attributes, kind, parentSpanId, resource, instrumentationLibrary) {
227
+ this.name = span.name;
227
228
  this.kind = kind;
228
229
  this.attributes = attributes;
229
230
  this.parentSpanId = parentSpanId;
230
231
  this.links = [];
231
232
  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();
233
+ this.startTime = this.dateToHrTime(span.startTime);
234
+ this.endTime = span.endTime ? this.dateToHrTime(span.endTime) : this.startTime;
235
+ this.ended = !!span.endTime;
236
+ if (span.endTime) {
237
+ const durationMs = span.endTime.getTime() - span.startTime.getTime();
237
238
  this.duration = [Math.floor(durationMs / 1e3), durationMs % 1e3 * 1e6];
238
239
  } else {
239
240
  this.duration = [0, 0];
240
241
  }
241
- if (aiSpan.errorInfo) {
242
+ if (span.errorInfo) {
242
243
  this.status = {
243
244
  code: SpanStatusCode.ERROR,
244
- message: aiSpan.errorInfo.message
245
+ message: span.errorInfo.message
245
246
  };
246
247
  this.events.push({
247
248
  name: "exception",
248
249
  attributes: {
249
- "exception.message": aiSpan.errorInfo.message,
250
+ "exception.message": span.errorInfo.message,
250
251
  "exception.type": "Error",
251
- ...aiSpan.errorInfo.details?.stack && {
252
- "exception.stacktrace": aiSpan.errorInfo.details.stack
252
+ ...span.errorInfo.details?.stack && {
253
+ "exception.stacktrace": span.errorInfo.details.stack
253
254
  }
254
255
  },
255
256
  time: this.startTime,
256
257
  droppedAttributesCount: 0
257
258
  });
258
- } else if (aiSpan.endTime) {
259
+ } else if (span.endTime) {
259
260
  this.status = { code: SpanStatusCode.OK };
260
261
  } else {
261
262
  this.status = { code: SpanStatusCode.UNSET };
262
263
  }
263
- if (aiSpan.isEvent) {
264
+ if (span.isEvent) {
264
265
  this.events.push({
265
266
  name: "instant_event",
266
267
  attributes: {},
@@ -269,14 +270,14 @@ var MastraReadableSpan = class {
269
270
  });
270
271
  }
271
272
  this.spanContext = () => ({
272
- traceId: aiSpan.traceId,
273
- spanId: aiSpan.id,
273
+ traceId: span.traceId,
274
+ spanId: span.id,
274
275
  traceFlags: TraceFlags.SAMPLED,
275
276
  isRemote: false
276
277
  });
277
278
  if (parentSpanId) {
278
279
  this.parentSpanContext = {
279
- traceId: aiSpan.traceId,
280
+ traceId: span.traceId,
280
281
  spanId: parentSpanId,
281
282
  traceFlags: TraceFlags.SAMPLED,
282
283
  isRemote: false
@@ -303,13 +304,13 @@ var MastraReadableSpan = class {
303
304
  // src/span-converter.ts
304
305
  var SPAN_KIND_MAPPING = {
305
306
  // Model operations are CLIENT spans (calling external AI services)
306
- [AISpanType.MODEL_GENERATION]: SpanKind.CLIENT,
307
- [AISpanType.MODEL_CHUNK]: SpanKind.CLIENT,
307
+ [SpanType.MODEL_GENERATION]: SpanKind.CLIENT,
308
+ [SpanType.MODEL_CHUNK]: SpanKind.CLIENT,
308
309
  // MCP tool calls are CLIENT (external service calls)
309
- [AISpanType.MCP_TOOL_CALL]: SpanKind.CLIENT,
310
+ [SpanType.MCP_TOOL_CALL]: SpanKind.CLIENT,
310
311
  // Root spans for agent/workflow are SERVER (entry points)
311
- [AISpanType.AGENT_RUN]: SpanKind.SERVER,
312
- [AISpanType.WORKFLOW_RUN]: SpanKind.SERVER
312
+ [SpanType.AGENT_RUN]: SpanKind.SERVER,
313
+ [SpanType.WORKFLOW_RUN]: SpanKind.SERVER
313
314
  };
314
315
  var SpanConverter = class {
315
316
  resource;
@@ -322,19 +323,19 @@ var SpanConverter = class {
322
323
  };
323
324
  }
324
325
  /**
325
- * Convert a Mastra AI span to an OpenTelemetry ReadableSpan
326
+ * Convert a Mastra Span to an OpenTelemetry ReadableSpan
326
327
  * This preserves Mastra's trace and span IDs
327
328
  */
328
- convertSpan(aiSpan) {
329
- const spanKind = this.getSpanKind(aiSpan);
330
- const attributes = this.buildAttributes(aiSpan);
331
- const spanName = this.buildSpanName(aiSpan);
332
- const otelSpan = { ...aiSpan, name: spanName };
329
+ convertSpan(Span) {
330
+ const spanKind = this.getSpanKind(Span);
331
+ const attributes = this.buildAttributes(Span);
332
+ const spanName = this.buildSpanName(Span);
333
+ const otelSpan = { ...Span, name: spanName };
333
334
  return new MastraReadableSpan(
334
335
  otelSpan,
335
336
  attributes,
336
337
  spanKind,
337
- aiSpan.parentSpanId,
338
+ Span.parentSpanId,
338
339
  // Use the parentSpanId from the Mastra span directly
339
340
  this.resource,
340
341
  this.instrumentationLibrary
@@ -343,81 +344,81 @@ var SpanConverter = class {
343
344
  /**
344
345
  * Get the appropriate SpanKind based on span type and context
345
346
  */
346
- getSpanKind(aiSpan) {
347
- if (aiSpan.isRootSpan) {
348
- if (aiSpan.type === AISpanType.AGENT_RUN || aiSpan.type === AISpanType.WORKFLOW_RUN) {
347
+ getSpanKind(Span) {
348
+ if (Span.isRootSpan) {
349
+ if (Span.type === SpanType.AGENT_RUN || Span.type === SpanType.WORKFLOW_RUN) {
349
350
  return SpanKind.SERVER;
350
351
  }
351
352
  }
352
- return SPAN_KIND_MAPPING[aiSpan.type] || SpanKind.INTERNAL;
353
+ return SPAN_KIND_MAPPING[Span.type] || SpanKind.INTERNAL;
353
354
  }
354
355
  /**
355
356
  * Build OTEL-compliant span name based on span type and attributes
356
357
  */
357
- buildSpanName(aiSpan) {
358
- switch (aiSpan.type) {
359
- case AISpanType.MODEL_GENERATION: {
360
- const attrs = aiSpan.attributes;
358
+ buildSpanName(Span) {
359
+ switch (Span.type) {
360
+ case SpanType.MODEL_GENERATION: {
361
+ const attrs = Span.attributes;
361
362
  const operation = attrs?.resultType === "tool_selection" ? "tool_selection" : "chat";
362
363
  const model = attrs?.model || "unknown";
363
364
  return `${operation} ${model}`;
364
365
  }
365
- case AISpanType.TOOL_CALL:
366
- case AISpanType.MCP_TOOL_CALL: {
367
- const toolAttrs = aiSpan.attributes;
366
+ case SpanType.TOOL_CALL:
367
+ case SpanType.MCP_TOOL_CALL: {
368
+ const toolAttrs = Span.attributes;
368
369
  const toolName = toolAttrs?.toolId || "unknown";
369
370
  return `tool.execute ${toolName}`;
370
371
  }
371
- case AISpanType.AGENT_RUN: {
372
- const agentAttrs = aiSpan.attributes;
372
+ case SpanType.AGENT_RUN: {
373
+ const agentAttrs = Span.attributes;
373
374
  const agentId = agentAttrs?.agentId || "unknown";
374
375
  return `agent.${agentId}`;
375
376
  }
376
- case AISpanType.WORKFLOW_RUN: {
377
- const workflowAttrs = aiSpan.attributes;
377
+ case SpanType.WORKFLOW_RUN: {
378
+ const workflowAttrs = Span.attributes;
378
379
  const workflowId = workflowAttrs?.workflowId || "unknown";
379
380
  return `workflow.${workflowId}`;
380
381
  }
381
- case AISpanType.WORKFLOW_STEP:
382
- return aiSpan.name;
382
+ case SpanType.WORKFLOW_STEP:
383
+ return Span.name;
383
384
  default:
384
- return aiSpan.name;
385
+ return Span.name;
385
386
  }
386
387
  }
387
388
  /**
388
- * Build OpenTelemetry attributes from Mastra AI span
389
+ * Build OpenTelemetry attributes from Mastra Span
389
390
  * Following OTEL Semantic Conventions for GenAI
390
391
  */
391
- buildAttributes(aiSpan) {
392
+ buildAttributes(Span) {
392
393
  const attributes = {};
393
- attributes["gen_ai.operation.name"] = this.getOperationName(aiSpan);
394
- attributes["span.kind"] = this.getSpanKindString(aiSpan);
395
- attributes["mastra.span.type"] = aiSpan.type;
396
- attributes["mastra.trace_id"] = aiSpan.traceId;
397
- attributes["mastra.span_id"] = aiSpan.id;
398
- if (aiSpan.parentSpanId) {
399
- attributes["mastra.parent_span_id"] = aiSpan.parentSpanId;
394
+ attributes["gen_ai.operation.name"] = this.getOperationName(Span);
395
+ attributes["span.kind"] = this.getSpanKindString(Span);
396
+ attributes["mastra.span.type"] = Span.type;
397
+ attributes["mastra.trace_id"] = Span.traceId;
398
+ attributes["mastra.span_id"] = Span.id;
399
+ if (Span.parentSpanId) {
400
+ attributes["mastra.parent_span_id"] = Span.parentSpanId;
400
401
  }
401
- if (aiSpan.input !== void 0) {
402
- const inputStr = typeof aiSpan.input === "string" ? aiSpan.input : JSON.stringify(aiSpan.input);
402
+ if (Span.input !== void 0) {
403
+ const inputStr = typeof Span.input === "string" ? Span.input : JSON.stringify(Span.input);
403
404
  attributes["input"] = inputStr;
404
- if (aiSpan.type === AISpanType.MODEL_GENERATION) {
405
+ if (Span.type === SpanType.MODEL_GENERATION) {
405
406
  attributes["gen_ai.prompt"] = inputStr;
406
- } else if (aiSpan.type === AISpanType.TOOL_CALL || aiSpan.type === AISpanType.MCP_TOOL_CALL) {
407
+ } else if (Span.type === SpanType.TOOL_CALL || Span.type === SpanType.MCP_TOOL_CALL) {
407
408
  attributes["gen_ai.tool.input"] = inputStr;
408
409
  }
409
410
  }
410
- if (aiSpan.output !== void 0) {
411
- const outputStr = typeof aiSpan.output === "string" ? aiSpan.output : JSON.stringify(aiSpan.output);
411
+ if (Span.output !== void 0) {
412
+ const outputStr = typeof Span.output === "string" ? Span.output : JSON.stringify(Span.output);
412
413
  attributes["output"] = outputStr;
413
- if (aiSpan.type === AISpanType.MODEL_GENERATION) {
414
+ if (Span.type === SpanType.MODEL_GENERATION) {
414
415
  attributes["gen_ai.completion"] = outputStr;
415
- } else if (aiSpan.type === AISpanType.TOOL_CALL || aiSpan.type === AISpanType.MCP_TOOL_CALL) {
416
+ } else if (Span.type === SpanType.TOOL_CALL || Span.type === SpanType.MCP_TOOL_CALL) {
416
417
  attributes["gen_ai.tool.output"] = outputStr;
417
418
  }
418
419
  }
419
- if (aiSpan.type === AISpanType.MODEL_GENERATION && aiSpan.attributes) {
420
- const modelAttrs = aiSpan.attributes;
420
+ if (Span.type === SpanType.MODEL_GENERATION && Span.attributes) {
421
+ const modelAttrs = Span.attributes;
421
422
  if (modelAttrs.model) {
422
423
  attributes["gen_ai.request.model"] = modelAttrs.model;
423
424
  }
@@ -470,12 +471,12 @@ var SpanConverter = class {
470
471
  attributes["gen_ai.response.finish_reasons"] = modelAttrs.finishReason;
471
472
  }
472
473
  }
473
- if ((aiSpan.type === AISpanType.TOOL_CALL || aiSpan.type === AISpanType.MCP_TOOL_CALL) && aiSpan.attributes) {
474
- const toolAttrs = aiSpan.attributes;
474
+ if ((Span.type === SpanType.TOOL_CALL || Span.type === SpanType.MCP_TOOL_CALL) && Span.attributes) {
475
+ const toolAttrs = Span.attributes;
475
476
  if (toolAttrs.toolId) {
476
477
  attributes["gen_ai.tool.name"] = toolAttrs.toolId;
477
478
  }
478
- if (aiSpan.type === AISpanType.MCP_TOOL_CALL) {
479
+ if (Span.type === SpanType.MCP_TOOL_CALL) {
479
480
  const mcpAttrs = toolAttrs;
480
481
  if (mcpAttrs.mcpServer) {
481
482
  attributes["mcp.server"] = mcpAttrs.mcpServer;
@@ -492,8 +493,8 @@ var SpanConverter = class {
492
493
  attributes["gen_ai.tool.success"] = toolAttrs.success;
493
494
  }
494
495
  }
495
- if (aiSpan.type === AISpanType.AGENT_RUN && aiSpan.attributes) {
496
- const agentAttrs = aiSpan.attributes;
496
+ if (Span.type === SpanType.AGENT_RUN && Span.attributes) {
497
+ const agentAttrs = Span.attributes;
497
498
  if (agentAttrs.agentId) {
498
499
  attributes["agent.id"] = agentAttrs.agentId;
499
500
  attributes["gen_ai.agent.id"] = agentAttrs.agentId;
@@ -505,8 +506,8 @@ var SpanConverter = class {
505
506
  attributes["agent.available_tools"] = JSON.stringify(agentAttrs.availableTools);
506
507
  }
507
508
  }
508
- if (aiSpan.type === AISpanType.WORKFLOW_RUN && aiSpan.attributes) {
509
- const workflowAttrs = aiSpan.attributes;
509
+ if (Span.type === SpanType.WORKFLOW_RUN && Span.attributes) {
510
+ const workflowAttrs = Span.attributes;
510
511
  if (workflowAttrs.workflowId) {
511
512
  attributes["workflow.id"] = workflowAttrs.workflowId;
512
513
  }
@@ -514,19 +515,19 @@ var SpanConverter = class {
514
515
  attributes["workflow.status"] = workflowAttrs.status;
515
516
  }
516
517
  }
517
- if (aiSpan.errorInfo) {
518
+ if (Span.errorInfo) {
518
519
  attributes["error"] = true;
519
- attributes["error.type"] = aiSpan.errorInfo.id || "unknown";
520
- attributes["error.message"] = aiSpan.errorInfo.message;
521
- if (aiSpan.errorInfo.domain) {
522
- attributes["error.domain"] = aiSpan.errorInfo.domain;
520
+ attributes["error.type"] = Span.errorInfo.id || "unknown";
521
+ attributes["error.message"] = Span.errorInfo.message;
522
+ if (Span.errorInfo.domain) {
523
+ attributes["error.domain"] = Span.errorInfo.domain;
523
524
  }
524
- if (aiSpan.errorInfo.category) {
525
- attributes["error.category"] = aiSpan.errorInfo.category;
525
+ if (Span.errorInfo.category) {
526
+ attributes["error.category"] = Span.errorInfo.category;
526
527
  }
527
528
  }
528
- if (aiSpan.metadata) {
529
- Object.entries(aiSpan.metadata).forEach(([key, value]) => {
529
+ if (Span.metadata) {
530
+ Object.entries(Span.metadata).forEach(([key, value]) => {
530
531
  if (!attributes[key]) {
531
532
  if (value === null || value === void 0) {
532
533
  return;
@@ -539,12 +540,12 @@ var SpanConverter = class {
539
540
  }
540
541
  });
541
542
  }
542
- if (aiSpan.startTime) {
543
- attributes["mastra.start_time"] = aiSpan.startTime.toISOString();
543
+ if (Span.startTime) {
544
+ attributes["mastra.start_time"] = Span.startTime.toISOString();
544
545
  }
545
- if (aiSpan.endTime) {
546
- attributes["mastra.end_time"] = aiSpan.endTime.toISOString();
547
- const duration = aiSpan.endTime.getTime() - aiSpan.startTime.getTime();
546
+ if (Span.endTime) {
547
+ attributes["mastra.end_time"] = Span.endTime.toISOString();
548
+ const duration = Span.endTime.getTime() - Span.startTime.getTime();
548
549
  attributes["mastra.duration_ms"] = duration;
549
550
  }
550
551
  return attributes;
@@ -552,28 +553,28 @@ var SpanConverter = class {
552
553
  /**
553
554
  * Get the operation name based on span type for gen_ai.operation.name
554
555
  */
555
- getOperationName(aiSpan) {
556
- switch (aiSpan.type) {
557
- case AISpanType.MODEL_GENERATION: {
558
- const attrs = aiSpan.attributes;
556
+ getOperationName(Span) {
557
+ switch (Span.type) {
558
+ case SpanType.MODEL_GENERATION: {
559
+ const attrs = Span.attributes;
559
560
  return attrs?.resultType === "tool_selection" ? "tool_selection" : "chat";
560
561
  }
561
- case AISpanType.TOOL_CALL:
562
- case AISpanType.MCP_TOOL_CALL:
562
+ case SpanType.TOOL_CALL:
563
+ case SpanType.MCP_TOOL_CALL:
563
564
  return "tool.execute";
564
- case AISpanType.AGENT_RUN:
565
+ case SpanType.AGENT_RUN:
565
566
  return "agent.run";
566
- case AISpanType.WORKFLOW_RUN:
567
+ case SpanType.WORKFLOW_RUN:
567
568
  return "workflow.run";
568
569
  default:
569
- return aiSpan.type.replace(/_/g, ".");
570
+ return Span.type.replace(/_/g, ".");
570
571
  }
571
572
  }
572
573
  /**
573
574
  * Get span kind as string for attribute
574
575
  */
575
- getSpanKindString(aiSpan) {
576
- const kind = this.getSpanKind(aiSpan);
576
+ getSpanKindString(Span) {
577
+ const kind = this.getSpanKind(Span);
577
578
  switch (kind) {
578
579
  case SpanKind.SERVER:
579
580
  return "server";
@@ -591,7 +592,7 @@ var SpanConverter = class {
591
592
  }
592
593
  };
593
594
 
594
- // src/ai-tracing.ts
595
+ // src/tracing.ts
595
596
  var OtelExporter = class extends BaseExporter {
596
597
  config;
597
598
  tracingConfig;
@@ -611,8 +612,8 @@ var OtelExporter = class extends BaseExporter {
611
612
  /**
612
613
  * Initialize with tracing configuration
613
614
  */
614
- init(config) {
615
- this.tracingConfig = config;
615
+ init(options) {
616
+ this.tracingConfig = options.config;
616
617
  }
617
618
  async setupExporter() {
618
619
  if (this.isSetup || this.exporter) return;
@@ -725,8 +726,8 @@ var OtelExporter = class extends BaseExporter {
725
726
  await this.setupProcessor();
726
727
  this.isSetup = true;
727
728
  }
728
- async _exportEvent(event) {
729
- if (event.type !== AITracingEventType.SPAN_ENDED) {
729
+ async _exportTracingEvent(event) {
730
+ if (event.type !== TracingEventType.SPAN_ENDED) {
730
731
  return;
731
732
  }
732
733
  const span = event.exportedSpan;