@mastra/observability 1.0.0-beta.10 → 1.0.0-beta.12

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
@@ -4161,12 +4161,19 @@ var observabilityRegistryConfigSchema = external_exports.object({
4161
4161
  var BaseExporter = class {
4162
4162
  /** Mastra logger instance */
4163
4163
  logger;
4164
+ /** Base configuration (accessible by subclasses) */
4165
+ baseConfig;
4164
4166
  /** Whether this exporter is disabled */
4165
- isDisabled = false;
4167
+ #disabled = false;
4168
+ /** Public getter for disabled state */
4169
+ get isDisabled() {
4170
+ return this.#disabled;
4171
+ }
4166
4172
  /**
4167
4173
  * Initialize the base exporter with logger
4168
4174
  */
4169
4175
  constructor(config = {}) {
4176
+ this.baseConfig = config;
4170
4177
  const logLevel = this.resolveLogLevel(config.logLevel);
4171
4178
  this.logger = config.logger ?? new ConsoleLogger({ level: logLevel, name: this.constructor.name });
4172
4179
  }
@@ -4201,20 +4208,50 @@ var BaseExporter = class {
4201
4208
  * @param reason - Reason why the exporter is disabled
4202
4209
  */
4203
4210
  setDisabled(reason) {
4204
- this.isDisabled = true;
4211
+ this.#disabled = true;
4205
4212
  this.logger.warn(`${this.name} disabled: ${reason}`);
4206
4213
  }
4214
+ /**
4215
+ * Apply the customSpanFormatter if configured.
4216
+ * This is called automatically by exportTracingEvent before _exportTracingEvent.
4217
+ *
4218
+ * Supports both synchronous and asynchronous formatters. If the formatter
4219
+ * returns a Promise, it will be awaited.
4220
+ *
4221
+ * @param event - The incoming tracing event
4222
+ * @returns The (possibly modified) event to process
4223
+ */
4224
+ async applySpanFormatter(event) {
4225
+ if (this.baseConfig.customSpanFormatter) {
4226
+ try {
4227
+ const formattedSpan = await this.baseConfig.customSpanFormatter(event.exportedSpan);
4228
+ return {
4229
+ ...event,
4230
+ exportedSpan: formattedSpan
4231
+ };
4232
+ } catch (error) {
4233
+ this.logger.error(`${this.name}: Error in customSpanFormatter`, {
4234
+ error,
4235
+ spanId: event.exportedSpan.id,
4236
+ traceId: event.exportedSpan.traceId
4237
+ });
4238
+ }
4239
+ }
4240
+ return event;
4241
+ }
4207
4242
  /**
4208
4243
  * Export a tracing event
4209
4244
  *
4210
- * This method checks if the exporter is disabled before calling _exportEvent.
4211
- * Subclasses should implement _exportEvent instead of overriding this method.
4245
+ * This method checks if the exporter is disabled, applies the customSpanFormatter,
4246
+ * then calls _exportTracingEvent.
4247
+ * Subclasses should implement _exportTracingEvent instead of overriding this method.
4212
4248
  */
4213
4249
  async exportTracingEvent(event) {
4214
4250
  if (this.isDisabled) {
4215
4251
  return;
4216
4252
  }
4217
- await this._exportTracingEvent(event);
4253
+ const processedEvent = await this.applySpanFormatter(event);
4254
+ await this._exportTracingEvent(processedEvent);
4218
4255
  }
4219
4256
  /**
4220
4257
  * Shutdown the exporter and clean up resources
@@ -4225,9 +4262,957 @@ var BaseExporter = class {
4225
4262
  this.logger.info(`${this.name} shutdown complete`);
4226
4263
  }
4227
4264
  };
4265
+ var TraceData = class {
4266
+ /** The vendor-specific root/trace object */
4267
+ #rootSpan;
4268
+ /** The span ID of the root span */
4269
+ #rootSpanId;
4270
+ /** Whether a span with isRootSpan=true has been successfully processed */
4271
+ #rootSpanProcessed;
4272
+ /** Maps eventId to vendor-specific event objects */
4273
+ #events;
4274
+ /** Maps spanId to vendor-specific span objects */
4275
+ #spans;
4276
+ /** Maps spanId to parentSpanId, representing the span hierarchy */
4277
+ #tree;
4278
+ /** Set of span IDs that have started but not yet ended */
4279
+ #activeSpanIds;
4280
+ /** Maps spanId to vendor-specific metadata */
4281
+ #metadata;
4282
+ /** Arbitrary key-value storage for per-trace data */
4283
+ #extraData;
4284
+ /** Events waiting for the root span to be processed */
4285
+ #waitingForRoot;
4286
+ /** Events waiting for specific parent spans, keyed by parentSpanId */
4287
+ #waitingForParent;
4288
+ /** When this trace data was created, used for cap enforcement */
4289
+ createdAt;
4290
+ constructor() {
4291
+ this.#events = /* @__PURE__ */ new Map();
4292
+ this.#spans = /* @__PURE__ */ new Map();
4293
+ this.#activeSpanIds = /* @__PURE__ */ new Set();
4294
+ this.#tree = /* @__PURE__ */ new Map();
4295
+ this.#metadata = /* @__PURE__ */ new Map();
4296
+ this.#extraData = /* @__PURE__ */ new Map();
4297
+ this.#rootSpanProcessed = false;
4298
+ this.#waitingForRoot = [];
4299
+ this.#waitingForParent = /* @__PURE__ */ new Map();
4300
+ this.createdAt = /* @__PURE__ */ new Date();
4301
+ }
4302
+ /**
4303
+ * Check if this trace has a root span registered.
4304
+ * @returns True if addRoot() has been called
4305
+ */
4306
+ hasRoot() {
4307
+ return !!this.#rootSpanId;
4308
+ }
4309
+ /**
4310
+ * Register the root span for this trace.
4311
+ * @param args.rootId - The span ID of the root span
4312
+ * @param args.rootData - The vendor-specific root object
4313
+ */
4314
+ addRoot(args) {
4315
+ this.#rootSpanId = args.rootId;
4316
+ this.#rootSpan = args.rootData;
4317
+ this.#rootSpanProcessed = true;
4318
+ }
4319
+ /**
4320
+ * Get the vendor-specific root object.
4321
+ * @returns The root object, or undefined if not yet set
4322
+ */
4323
+ getRoot() {
4324
+ return this.#rootSpan;
4325
+ }
4326
+ /**
4327
+ * Check if a span with isRootSpan=true has been successfully processed.
4328
+ * Set via addRoot() or markRootSpanProcessed().
4329
+ * @returns True if the root span has been processed
4330
+ */
4331
+ isRootProcessed() {
4332
+ return this.#rootSpanProcessed;
4333
+ }
4334
+ /**
4335
+ * Mark that the root span has been processed.
4336
+ * Used by exporters with skipBuildRootTask=true where root goes through _buildSpan
4337
+ * instead of _buildRoot.
4338
+ */
4339
+ markRootSpanProcessed() {
4340
+ this.#rootSpanProcessed = true;
4341
+ }
4342
+ /**
4343
+ * Store an arbitrary value in per-trace storage.
4344
+ * @param key - Storage key
4345
+ * @param value - Value to store
4346
+ */
4347
+ setExtraValue(key, value) {
4348
+ this.#extraData.set(key, value);
4349
+ }
4350
+ /**
4351
+ * Check if a key exists in per-trace storage.
4352
+ * @param key - Storage key
4353
+ * @returns True if the key exists
4354
+ */
4355
+ hasExtraValue(key) {
4356
+ return this.#extraData.has(key);
4357
+ }
4358
+ /**
4359
+ * Get a value from per-trace storage.
4360
+ * @param key - Storage key
4361
+ * @returns The stored value, or undefined if not found
4362
+ */
4363
+ getExtraValue(key) {
4364
+ return this.#extraData.get(key);
4365
+ }
4366
+ // ============================================================================
4367
+ // Early Queue Methods
4368
+ // ============================================================================
4369
+ /**
4370
+ * Add an event to the waiting queue.
4371
+ * @param args.event - The tracing event to queue
4372
+ * @param args.waitingFor - 'root' or a specific parentSpanId
4373
+ * @param args.attempts - Optional: preserve attempts count when re-queuing
4374
+ * @param args.queuedAt - Optional: preserve original queue time when re-queuing
4375
+ */
4376
+ addToWaitingQueue(args) {
4377
+ const queuedEvent = {
4378
+ event: args.event,
4379
+ waitingFor: args.waitingFor,
4380
+ attempts: args.attempts ?? 0,
4381
+ queuedAt: args.queuedAt ?? /* @__PURE__ */ new Date()
4382
+ };
4383
+ if (args.waitingFor === "root") {
4384
+ this.#waitingForRoot.push(queuedEvent);
4385
+ } else {
4386
+ const queue = this.#waitingForParent.get(args.waitingFor) ?? [];
4387
+ queue.push(queuedEvent);
4388
+ this.#waitingForParent.set(args.waitingFor, queue);
4389
+ }
4390
+ }
4391
+ /**
4392
+ * Get all events waiting for the root span.
4393
+ * Returns a copy of the internal array.
4394
+ */
4395
+ getEventsWaitingForRoot() {
4396
+ return [...this.#waitingForRoot];
4397
+ }
4398
+ /**
4399
+ * Get all events waiting for a specific parent span.
4400
+ * Returns a copy of the internal array.
4401
+ */
4402
+ getEventsWaitingFor(args) {
4403
+ return [...this.#waitingForParent.get(args.spanId) ?? []];
4404
+ }
4405
+ /**
4406
+ * Clear the waiting-for-root queue.
4407
+ */
4408
+ clearWaitingForRoot() {
4409
+ this.#waitingForRoot = [];
4410
+ }
4411
+ /**
4412
+ * Clear the waiting queue for a specific parent span.
4413
+ */
4414
+ clearWaitingFor(args) {
4415
+ this.#waitingForParent.delete(args.spanId);
4416
+ }
4417
+ /**
4418
+ * Get total count of events in all waiting queues.
4419
+ */
4420
+ waitingQueueSize() {
4421
+ let count = this.#waitingForRoot.length;
4422
+ for (const queue of this.#waitingForParent.values()) {
4423
+ count += queue.length;
4424
+ }
4425
+ return count;
4426
+ }
4427
+ /**
4428
+ * Get all queued events across all waiting queues.
4429
+ * Used for cleanup and logging orphaned events.
4430
+ * @returns Array of all queued events
4431
+ */
4432
+ getAllQueuedEvents() {
4433
+ const all = [...this.#waitingForRoot];
4434
+ for (const queue of this.#waitingForParent.values()) {
4435
+ all.push(...queue);
4436
+ }
4437
+ return all;
4438
+ }
4439
+ // ============================================================================
4440
+ // Span Tree Methods
4441
+ // ============================================================================
4442
+ /**
4443
+ * Record the parent-child relationship for a span.
4444
+ * @param args.spanId - The child span ID
4445
+ * @param args.parentSpanId - The parent span ID, or undefined for root spans
4446
+ */
4447
+ addBranch(args) {
4448
+ this.#tree.set(args.spanId, args.parentSpanId);
4449
+ }
4450
+ /**
4451
+ * Get the parent span ID for a given span.
4452
+ * @param args.spanId - The span ID to look up
4453
+ * @returns The parent span ID, or undefined if root or not found
4454
+ */
4455
+ getParentId(args) {
4456
+ return this.#tree.get(args.spanId);
4457
+ }
4458
+ // ============================================================================
4459
+ // Span Management Methods
4460
+ // ============================================================================
4461
+ /**
4462
+ * Register a span and mark it as active.
4463
+ * @param args.spanId - The span ID
4464
+ * @param args.spanData - The vendor-specific span object
4465
+ */
4466
+ addSpan(args) {
4467
+ this.#spans.set(args.spanId, args.spanData);
4468
+ this.#activeSpanIds.add(args.spanId);
4469
+ }
4470
+ /**
4471
+ * Check if a span exists (regardless of active state).
4472
+ * @param args.spanId - The span ID to check
4473
+ * @returns True if the span exists
4474
+ */
4475
+ hasSpan(args) {
4476
+ return this.#spans.has(args.spanId);
4477
+ }
4478
+ /**
4479
+ * Get a span by ID.
4480
+ * @param args.spanId - The span ID to look up
4481
+ * @returns The vendor-specific span object, or undefined if not found
4482
+ */
4483
+ getSpan(args) {
4484
+ return this.#spans.get(args.spanId);
4485
+ }
4486
+ /**
4487
+ * Mark a span as ended (no longer active).
4488
+ * @param args.spanId - The span ID to mark as ended
4489
+ */
4490
+ endSpan(args) {
4491
+ this.#activeSpanIds.delete(args.spanId);
4492
+ }
4493
+ /**
4494
+ * Check if a span is currently active (started but not ended).
4495
+ * @param args.spanId - The span ID to check
4496
+ * @returns True if the span is active
4497
+ */
4498
+ isActiveSpan(args) {
4499
+ return this.#activeSpanIds.has(args.spanId);
4500
+ }
4501
+ /**
4502
+ * Get the count of currently active spans.
4503
+ * @returns Number of active spans
4504
+ */
4505
+ activeSpanCount() {
4506
+ return this.#activeSpanIds.size;
4507
+ }
4508
+ /**
4509
+ * Get all active span IDs.
4510
+ * @returns Array of active span IDs
4511
+ */
4512
+ get activeSpanIds() {
4513
+ return [...this.#activeSpanIds];
4514
+ }
4515
+ // ============================================================================
4516
+ // Event Management Methods
4517
+ // ============================================================================
4518
+ /**
4519
+ * Register an event.
4520
+ * @param args.eventId - The event ID
4521
+ * @param args.eventData - The vendor-specific event object
4522
+ */
4523
+ addEvent(args) {
4524
+ this.#events.set(args.eventId, args.eventData);
4525
+ }
4526
+ // ============================================================================
4527
+ // Metadata Methods
4528
+ // ============================================================================
4529
+ /**
4530
+ * Store vendor-specific metadata for a span.
4531
+ * Note: This overwrites any existing metadata for the span.
4532
+ * @param args.spanId - The span ID
4533
+ * @param args.metadata - The vendor-specific metadata
4534
+ */
4535
+ addMetadata(args) {
4536
+ this.#metadata.set(args.spanId, args.metadata);
4537
+ }
4538
+ /**
4539
+ * Get vendor-specific metadata for a span.
4540
+ * @param args.spanId - The span ID
4541
+ * @returns The metadata, or undefined if not found
4542
+ */
4543
+ getMetadata(args) {
4544
+ return this.#metadata.get(args.spanId);
4545
+ }
4546
+ // ============================================================================
4547
+ // Parent Lookup Methods
4548
+ // ============================================================================
4549
+ /**
4550
+ * Get the parent span or event for a given span.
4551
+ * Looks up in both spans and events maps.
4552
+ * @param args.span - The span to find the parent for
4553
+ * @returns The parent span/event object, or undefined if root or not found
4554
+ */
4555
+ getParent(args) {
4556
+ const parentId = args.span.parentSpanId;
4557
+ if (parentId) {
4558
+ if (this.#spans.has(parentId)) {
4559
+ return this.#spans.get(parentId);
4560
+ }
4561
+ if (this.#events.has(parentId)) {
4562
+ return this.#events.get(parentId);
4563
+ }
4564
+ }
4565
+ return void 0;
4566
+ }
4567
+ /**
4568
+ * Get the parent span/event or fall back to the root object.
4569
+ * Useful for vendors that attach child spans to either parent spans or the trace root.
4570
+ * @param args.span - The span to find the parent for
4571
+ * @returns The parent span/event, the root object, or undefined
4572
+ */
4573
+ getParentOrRoot(args) {
4574
+ return this.getParent(args) ?? this.getRoot();
4575
+ }
4576
+ };
4577
+ var DEFAULT_EARLY_QUEUE_MAX_ATTEMPTS = 5;
4578
+ var DEFAULT_EARLY_QUEUE_TTL_MS = 3e4;
4579
+ var DEFAULT_TRACE_CLEANUP_DELAY_MS = 3e4;
4580
+ var DEFAULT_MAX_PENDING_CLEANUP_TRACES = 100;
4581
+ var DEFAULT_MAX_TOTAL_TRACES = 500;
4582
+ var TrackingExporter = class extends BaseExporter {
4583
+ /** Map of traceId to per-trace data container */
4584
+ #traceMap = /* @__PURE__ */ new Map();
4585
+ /** Flag to prevent processing during shutdown */
4586
+ #shutdownStarted = false;
4587
+ /** Flag to prevent concurrent hard cap enforcement */
4588
+ #hardCapEnforcementInProgress = false;
4589
+ /** Map of traceId to scheduled cleanup timeout */
4590
+ #pendingCleanups = /* @__PURE__ */ new Map();
4591
+ // Note: #traceMap maintains insertion order (JS Map spec), so we use
4592
+ // #traceMap.keys() to iterate traces oldest-first for cap enforcement.
4593
+ /** Subclass configuration with resolved values */
4594
+ config;
4595
+ /** Maximum attempts to process a queued event before dropping */
4596
+ #earlyQueueMaxAttempts;
4597
+ /** TTL in milliseconds for queued events */
4598
+ #earlyQueueTTLMs;
4599
+ /** Delay before cleaning up completed traces */
4600
+ #traceCleanupDelayMs;
4601
+ /** Soft cap on traces awaiting cleanup */
4602
+ #maxPendingCleanupTraces;
4603
+ /** Hard cap on total traces (will abort active spans if exceeded) */
4604
+ #maxTotalTraces;
4605
+ constructor(config) {
4606
+ super(config);
4607
+ this.config = config;
4608
+ this.#earlyQueueMaxAttempts = config.earlyQueueMaxAttempts ?? DEFAULT_EARLY_QUEUE_MAX_ATTEMPTS;
4609
+ this.#earlyQueueTTLMs = config.earlyQueueTTLMs ?? DEFAULT_EARLY_QUEUE_TTL_MS;
4610
+ this.#traceCleanupDelayMs = config.traceCleanupDelayMs ?? DEFAULT_TRACE_CLEANUP_DELAY_MS;
4611
+ this.#maxPendingCleanupTraces = config.maxPendingCleanupTraces ?? DEFAULT_MAX_PENDING_CLEANUP_TRACES;
4612
+ this.#maxTotalTraces = config.maxTotalTraces ?? DEFAULT_MAX_TOTAL_TRACES;
4613
+ }
4614
+ // ============================================================================
4615
+ // Early Queue Processing
4616
+ // ============================================================================
4617
+ /**
4618
+ * Schedule async processing of events waiting for root span.
4619
+ * Called after root span is successfully processed.
4620
+ */
4621
+ #scheduleProcessWaitingForRoot(traceId) {
4622
+ setImmediate(() => {
4623
+ this.#processWaitingForRoot(traceId).catch((error) => {
4624
+ this.logger.error(`${this.name}: Error processing waiting-for-root queue`, { error, traceId });
4625
+ });
4626
+ });
4627
+ }
4628
+ /**
4629
+ * Schedule async processing of events waiting for a specific parent span.
4630
+ * Called after a span/event is successfully created.
4631
+ */
4632
+ #scheduleProcessWaitingFor(traceId, spanId) {
4633
+ setImmediate(() => {
4634
+ this.#processWaitingFor(traceId, spanId).catch((error) => {
4635
+ this.logger.error(`${this.name}: Error processing waiting queue`, { error, traceId, spanId });
4636
+ });
4637
+ });
4638
+ }
4639
+ /**
4640
+ * Process all events waiting for root span.
4641
+ */
4642
+ async #processWaitingForRoot(traceId) {
4643
+ if (this.#shutdownStarted) return;
4644
+ const traceData = this.#traceMap.get(traceId);
4645
+ if (!traceData) return;
4646
+ const queue = traceData.getEventsWaitingForRoot();
4647
+ if (queue.length === 0) return;
4648
+ this.logger.debug(`${this.name}: Processing ${queue.length} events waiting for root`, { traceId });
4649
+ const toKeep = [];
4650
+ const now = Date.now();
4651
+ for (const queuedEvent of queue) {
4652
+ if (now - queuedEvent.queuedAt.getTime() > this.#earlyQueueTTLMs) {
4653
+ this.logger.warn(`${this.name}: Dropping event due to TTL expiry`, {
4654
+ traceId,
4655
+ spanId: queuedEvent.event.exportedSpan.id,
4656
+ waitingFor: queuedEvent.waitingFor,
4657
+ queuedAt: queuedEvent.queuedAt,
4658
+ attempts: queuedEvent.attempts
4659
+ });
4660
+ continue;
4661
+ }
4662
+ if (queuedEvent.attempts >= this.#earlyQueueMaxAttempts) {
4663
+ this.logger.warn(`${this.name}: Dropping event due to max attempts`, {
4664
+ traceId,
4665
+ spanId: queuedEvent.event.exportedSpan.id,
4666
+ waitingFor: queuedEvent.waitingFor,
4667
+ attempts: queuedEvent.attempts
4668
+ });
4669
+ continue;
4670
+ }
4671
+ queuedEvent.attempts++;
4672
+ const processed = await this.#tryProcessQueuedEvent(queuedEvent, traceData);
4673
+ if (!processed) {
4674
+ const parentId = queuedEvent.event.exportedSpan.parentSpanId;
4675
+ if (parentId && traceData.isRootProcessed()) {
4676
+ traceData.addToWaitingQueue({
4677
+ event: queuedEvent.event,
4678
+ waitingFor: parentId,
4679
+ attempts: queuedEvent.attempts,
4680
+ queuedAt: queuedEvent.queuedAt
4681
+ });
4682
+ } else {
4683
+ toKeep.push(queuedEvent);
4684
+ }
4685
+ }
4686
+ }
4687
+ traceData.clearWaitingForRoot();
4688
+ for (const event of toKeep) {
4689
+ traceData.addToWaitingQueue({
4690
+ event: event.event,
4691
+ waitingFor: "root",
4692
+ attempts: event.attempts,
4693
+ queuedAt: event.queuedAt
4694
+ });
4695
+ }
4696
+ }
4697
+ /**
4698
+ * Process events waiting for a specific parent span.
4699
+ */
4700
+ async #processWaitingFor(traceId, spanId) {
4701
+ if (this.#shutdownStarted) return;
4702
+ const traceData = this.#traceMap.get(traceId);
4703
+ if (!traceData) return;
4704
+ const queue = traceData.getEventsWaitingFor({ spanId });
4705
+ if (queue.length === 0) return;
4706
+ this.logger.debug(`${this.name}: Processing ${queue.length} events waiting for span`, { traceId, spanId });
4707
+ const toKeep = [];
4708
+ const now = Date.now();
4709
+ for (const queuedEvent of queue) {
4710
+ if (now - queuedEvent.queuedAt.getTime() > this.#earlyQueueTTLMs) {
4711
+ this.logger.warn(`${this.name}: Dropping event due to TTL expiry`, {
4712
+ traceId,
4713
+ spanId: queuedEvent.event.exportedSpan.id,
4714
+ waitingFor: queuedEvent.waitingFor,
4715
+ queuedAt: queuedEvent.queuedAt,
4716
+ attempts: queuedEvent.attempts
4717
+ });
4718
+ continue;
4719
+ }
4720
+ if (queuedEvent.attempts >= this.#earlyQueueMaxAttempts) {
4721
+ this.logger.warn(`${this.name}: Dropping event due to max attempts`, {
4722
+ traceId,
4723
+ spanId: queuedEvent.event.exportedSpan.id,
4724
+ waitingFor: queuedEvent.waitingFor,
4725
+ attempts: queuedEvent.attempts
4726
+ });
4727
+ continue;
4728
+ }
4729
+ queuedEvent.attempts++;
4730
+ const processed = await this.#tryProcessQueuedEvent(queuedEvent, traceData);
4731
+ if (!processed) {
4732
+ toKeep.push(queuedEvent);
4733
+ }
4734
+ }
4735
+ traceData.clearWaitingFor({ spanId });
4736
+ for (const event of toKeep) {
4737
+ traceData.addToWaitingQueue({
4738
+ event: event.event,
4739
+ waitingFor: spanId,
4740
+ attempts: event.attempts,
4741
+ queuedAt: event.queuedAt
4742
+ });
4743
+ }
4744
+ }
4745
+ /**
4746
+ * Try to process a queued event.
4747
+ * Returns true if successfully processed, false if still waiting for dependencies.
4748
+ */
4749
+ async #tryProcessQueuedEvent(queuedEvent, traceData) {
4750
+ const { event } = queuedEvent;
4751
+ const { exportedSpan } = event;
4752
+ const method = this.getMethod(event);
4753
+ try {
4754
+ switch (method) {
4755
+ case "handleEventSpan": {
4756
+ traceData.addBranch({ spanId: exportedSpan.id, parentSpanId: exportedSpan.parentSpanId });
4757
+ const eventData = await this._buildEvent({ span: exportedSpan, traceData });
4758
+ if (eventData) {
4759
+ if (!this.skipCachingEventSpans) {
4760
+ traceData.addEvent({ eventId: exportedSpan.id, eventData });
4761
+ }
4762
+ this.#scheduleProcessWaitingFor(exportedSpan.traceId, exportedSpan.id);
4763
+ return true;
4764
+ }
4765
+ return false;
4766
+ }
4767
+ case "handleSpanStart": {
4768
+ traceData.addBranch({ spanId: exportedSpan.id, parentSpanId: exportedSpan.parentSpanId });
4769
+ const spanData = await this._buildSpan({ span: exportedSpan, traceData });
4770
+ if (spanData) {
4771
+ traceData.addSpan({ spanId: exportedSpan.id, spanData });
4772
+ if (exportedSpan.isRootSpan) {
4773
+ traceData.markRootSpanProcessed();
4774
+ }
4775
+ this.#scheduleProcessWaitingFor(exportedSpan.traceId, exportedSpan.id);
4776
+ return true;
4777
+ }
4778
+ return false;
4779
+ }
4780
+ case "handleSpanUpdate": {
4781
+ await this._updateSpan({ span: exportedSpan, traceData });
4782
+ return true;
4783
+ }
4784
+ case "handleSpanEnd": {
4785
+ traceData.endSpan({ spanId: exportedSpan.id });
4786
+ await this._finishSpan({ span: exportedSpan, traceData });
4787
+ if (traceData.activeSpanCount() === 0) {
4788
+ this.#scheduleCleanup(exportedSpan.traceId);
4789
+ }
4790
+ return true;
4791
+ }
4792
+ default:
4793
+ return false;
4794
+ }
4795
+ } catch (error) {
4796
+ this.logger.error(`${this.name}: Error processing queued event`, { error, event, method });
4797
+ return false;
4798
+ }
4799
+ }
4800
+ // ============================================================================
4801
+ // Delayed Cleanup
4802
+ // ============================================================================
4803
+ /**
4804
+ * Schedule cleanup of trace data after a delay.
4805
+ * Allows late-arriving data to still be processed.
4806
+ */
4807
+ #scheduleCleanup(traceId) {
4808
+ this.#cancelScheduledCleanup(traceId);
4809
+ this.logger.debug(`${this.name}: Scheduling cleanup in ${this.#traceCleanupDelayMs}ms`, { traceId });
4810
+ const timeout = setTimeout(() => {
4811
+ this.#pendingCleanups.delete(traceId);
4812
+ this.#performCleanup(traceId);
4813
+ }, this.#traceCleanupDelayMs);
4814
+ this.#pendingCleanups.set(traceId, timeout);
4815
+ this.#enforcePendingCleanupCap();
4816
+ }
4817
+ /**
4818
+ * Cancel a scheduled cleanup for a trace.
4819
+ */
4820
+ #cancelScheduledCleanup(traceId) {
4821
+ const existingTimeout = this.#pendingCleanups.get(traceId);
4822
+ if (existingTimeout) {
4823
+ clearTimeout(existingTimeout);
4824
+ this.#pendingCleanups.delete(traceId);
4825
+ this.logger.debug(`${this.name}: Cancelled scheduled cleanup`, { traceId });
4826
+ }
4827
+ }
4828
+ /**
4829
+ * Perform the actual cleanup of trace data.
4830
+ */
4831
+ #performCleanup(traceId) {
4832
+ const traceData = this.#traceMap.get(traceId);
4833
+ if (!traceData) return;
4834
+ const orphanedEvents = traceData.getAllQueuedEvents();
4835
+ if (orphanedEvents.length > 0) {
4836
+ this.logger.warn(`${this.name}: Dropping ${orphanedEvents.length} orphaned events on cleanup`, {
4837
+ traceId,
4838
+ orphanedEvents: orphanedEvents.map((e) => ({
4839
+ spanId: e.event.exportedSpan.id,
4840
+ waitingFor: e.waitingFor,
4841
+ attempts: e.attempts,
4842
+ queuedAt: e.queuedAt
4843
+ }))
4844
+ });
4845
+ }
4846
+ this.#traceMap.delete(traceId);
4847
+ this.logger.debug(`${this.name}: Cleaned up trace data`, { traceId });
4848
+ }
4849
+ // ============================================================================
4850
+ // Cap Enforcement
4851
+ // ============================================================================
4852
+ /**
4853
+ * Enforce soft cap on pending cleanup traces.
4854
+ * Only removes traces with activeSpanCount == 0.
4855
+ */
4856
+ #enforcePendingCleanupCap() {
4857
+ if (this.#pendingCleanups.size <= this.#maxPendingCleanupTraces) {
4858
+ return;
4859
+ }
4860
+ const toRemove = this.#pendingCleanups.size - this.#maxPendingCleanupTraces;
4861
+ this.logger.warn(`${this.name}: Pending cleanup cap exceeded, force-cleaning ${toRemove} traces`, {
4862
+ pendingCount: this.#pendingCleanups.size,
4863
+ cap: this.#maxPendingCleanupTraces
4864
+ });
4865
+ let removed = 0;
4866
+ for (const traceId of this.#traceMap.keys()) {
4867
+ if (removed >= toRemove) break;
4868
+ if (this.#pendingCleanups.has(traceId)) {
4869
+ this.#cancelScheduledCleanup(traceId);
4870
+ this.#performCleanup(traceId);
4871
+ removed++;
4872
+ }
4873
+ }
4874
+ }
4875
+ /**
4876
+ * Enforce hard cap on total traces.
4877
+ * Will kill even active traces if necessary.
4878
+ * Uses a flag to prevent concurrent executions when called fire-and-forget.
4879
+ */
4880
+ async #enforceHardCap() {
4881
+ if (this.#traceMap.size <= this.#maxTotalTraces || this.#hardCapEnforcementInProgress) {
4882
+ return;
4883
+ }
4884
+ this.#hardCapEnforcementInProgress = true;
4885
+ try {
4886
+ if (this.#traceMap.size <= this.#maxTotalTraces) {
4887
+ return;
4888
+ }
4889
+ const toRemove = this.#traceMap.size - this.#maxTotalTraces;
4890
+ this.logger.warn(`${this.name}: Total trace cap exceeded, killing ${toRemove} oldest traces`, {
4891
+ traceCount: this.#traceMap.size,
4892
+ cap: this.#maxTotalTraces
4893
+ });
4894
+ const reason = {
4895
+ id: "TRACE_CAP_EXCEEDED",
4896
+ message: "Trace killed due to memory cap enforcement.",
4897
+ domain: "MASTRA_OBSERVABILITY",
4898
+ category: "SYSTEM"
4899
+ };
4900
+ let removed = 0;
4901
+ for (const traceId of [...this.#traceMap.keys()]) {
4902
+ if (removed >= toRemove) break;
4903
+ const traceData = this.#traceMap.get(traceId);
4904
+ if (traceData) {
4905
+ for (const spanId of traceData.activeSpanIds) {
4906
+ const span = traceData.getSpan({ spanId });
4907
+ if (span) {
4908
+ await this._abortSpan({ span, traceData, reason });
4909
+ }
4910
+ }
4911
+ this.#cancelScheduledCleanup(traceId);
4912
+ this.#performCleanup(traceId);
4913
+ removed++;
4914
+ }
4915
+ }
4916
+ } finally {
4917
+ this.#hardCapEnforcementInProgress = false;
4918
+ }
4919
+ }
4920
+ // ============================================================================
4921
+ // Lifecycle Hooks (Override in subclass)
4922
+ // ============================================================================
4923
+ /**
4924
+ * Hook called before processing each tracing event.
4925
+ * Override to transform or enrich the event before processing.
4926
+ *
4927
+ * Note: The customSpanFormatter is applied at the BaseExporter level before this hook.
4928
+ * Subclasses can override this to add additional pre-processing logic.
4929
+ *
4930
+ * @param event - The incoming tracing event
4931
+ * @returns The (possibly modified) event to process
4932
+ */
4933
+ async _preExportTracingEvent(event) {
4934
+ return event;
4935
+ }
4936
+ /**
4937
+ * Hook called after processing each tracing event.
4938
+ * Override to perform post-processing actions like flushing.
4939
+ */
4940
+ async _postExportTracingEvent() {
4941
+ }
4942
+ // ============================================================================
4943
+ // Behavior Flags (Override in subclass as needed)
4944
+ // ============================================================================
4945
+ /**
4946
+ * If true, skip calling _buildRoot and let root spans go through _buildSpan.
4947
+ * Use when the vendor doesn't have a separate trace/root concept.
4948
+ * @default false
4949
+ */
4950
+ skipBuildRootTask = false;
4951
+ /**
4952
+ * If true, skip processing span_updated events entirely.
4953
+ * Use when the vendor doesn't support incremental span updates.
4954
+ * @default false
4955
+ */
4956
+ skipSpanUpdateEvents = false;
4957
+ /**
4958
+ * If true, don't cache event spans in TraceData.
4959
+ * Use when events can't be parents of other spans.
4960
+ * @default false
4961
+ */
4962
+ skipCachingEventSpans = false;
4963
+ getMethod(event) {
4964
+ if (event.exportedSpan.isEvent) {
4965
+ return "handleEventSpan";
4966
+ }
4967
+ const eventType = event.type;
4968
+ switch (eventType) {
4969
+ case TracingEventType.SPAN_STARTED:
4970
+ return "handleSpanStart";
4971
+ case TracingEventType.SPAN_UPDATED:
4972
+ return "handleSpanUpdate";
4973
+ case TracingEventType.SPAN_ENDED:
4974
+ return "handleSpanEnd";
4975
+ default: {
4976
+ const _exhaustiveCheck = eventType;
4977
+ throw new Error(`Unhandled event type: ${_exhaustiveCheck}`);
4978
+ }
4979
+ }
4980
+ }
4981
+ async _exportTracingEvent(event) {
4982
+ if (this.#shutdownStarted) {
4983
+ return;
4984
+ }
4985
+ const method = this.getMethod(event);
4986
+ if (method == "handleSpanUpdate" && this.skipSpanUpdateEvents) {
4987
+ return;
4988
+ }
4989
+ const traceId = event.exportedSpan.traceId;
4990
+ const traceData = this.getTraceData({ traceId, method });
4991
+ const { exportedSpan } = await this._preExportTracingEvent(event);
4992
+ if (!this.skipBuildRootTask && !traceData.hasRoot()) {
4993
+ if (exportedSpan.isRootSpan) {
4994
+ this.logger.debug(`${this.name}: Building root`, {
4995
+ traceId: exportedSpan.traceId,
4996
+ spanId: exportedSpan.id
4997
+ });
4998
+ const rootData = await this._buildRoot({ span: exportedSpan, traceData });
4999
+ if (rootData) {
5000
+ this.logger.debug(`${this.name}: Adding root`, {
5001
+ traceId: exportedSpan.traceId,
5002
+ spanId: exportedSpan.id
5003
+ });
5004
+ traceData.addRoot({ rootId: exportedSpan.id, rootData });
5005
+ this.#scheduleProcessWaitingForRoot(traceId);
5006
+ }
5007
+ } else {
5008
+ this.logger.debug(`${this.name}: Root does not exist, adding span to waiting queue.`, {
5009
+ traceId: exportedSpan.traceId,
5010
+ spanId: exportedSpan.id
5011
+ });
5012
+ traceData.addToWaitingQueue({ event, waitingFor: "root" });
5013
+ return;
5014
+ }
5015
+ }
5016
+ if (exportedSpan.metadata && this.name in exportedSpan.metadata) {
5017
+ const metadata = exportedSpan.metadata[this.name];
5018
+ this.logger.debug(`${this.name}: Found provider metadata in span`, {
5019
+ traceId: exportedSpan.traceId,
5020
+ spanId: exportedSpan.id,
5021
+ metadata
5022
+ });
5023
+ traceData.addMetadata({ spanId: exportedSpan.id, metadata });
5024
+ }
5025
+ try {
5026
+ switch (method) {
5027
+ case "handleEventSpan": {
5028
+ this.logger.debug(`${this.name}: handling event`, {
5029
+ traceId: exportedSpan.traceId,
5030
+ spanId: exportedSpan.id
5031
+ });
5032
+ traceData.addBranch({ spanId: exportedSpan.id, parentSpanId: exportedSpan.parentSpanId });
5033
+ const eventData = await this._buildEvent({ span: exportedSpan, traceData });
5034
+ if (eventData) {
5035
+ if (!this.skipCachingEventSpans) {
5036
+ this.logger.debug(`${this.name}: adding event to traceData`, {
5037
+ traceId: exportedSpan.traceId,
5038
+ spanId: exportedSpan.id
5039
+ });
5040
+ traceData.addEvent({ eventId: exportedSpan.id, eventData });
5041
+ }
5042
+ this.#scheduleProcessWaitingFor(traceId, exportedSpan.id);
5043
+ } else {
5044
+ const parentId = exportedSpan.parentSpanId;
5045
+ this.logger.debug(`${this.name}: adding event to waiting queue`, {
5046
+ traceId: exportedSpan.traceId,
5047
+ spanId: exportedSpan.id,
5048
+ waitingFor: parentId ?? "root"
5049
+ });
5050
+ traceData.addToWaitingQueue({ event, waitingFor: parentId ?? "root" });
5051
+ }
5052
+ break;
5053
+ }
5054
+ case "handleSpanStart": {
5055
+ this.logger.debug(`${this.name}: handling span start`, {
5056
+ traceId: exportedSpan.traceId,
5057
+ spanId: exportedSpan.id
5058
+ });
5059
+ traceData.addBranch({ spanId: exportedSpan.id, parentSpanId: exportedSpan.parentSpanId });
5060
+ const spanData = await this._buildSpan({ span: exportedSpan, traceData });
5061
+ if (spanData) {
5062
+ this.logger.debug(`${this.name}: adding span to traceData`, {
5063
+ traceId: exportedSpan.traceId,
5064
+ spanId: exportedSpan.id
5065
+ });
5066
+ traceData.addSpan({ spanId: exportedSpan.id, spanData });
5067
+ if (exportedSpan.isRootSpan) {
5068
+ traceData.markRootSpanProcessed();
5069
+ this.#scheduleProcessWaitingForRoot(traceId);
5070
+ }
5071
+ this.#scheduleProcessWaitingFor(traceId, exportedSpan.id);
5072
+ } else {
5073
+ const parentId = exportedSpan.parentSpanId;
5074
+ this.logger.debug(`${this.name}: adding span to waiting queue`, {
5075
+ traceId: exportedSpan.traceId,
5076
+ waitingFor: parentId ?? "root"
5077
+ });
5078
+ traceData.addToWaitingQueue({ event, waitingFor: parentId ?? "root" });
5079
+ }
5080
+ break;
5081
+ }
5082
+ case "handleSpanUpdate":
5083
+ this.logger.debug(`${this.name}: handling span update`, {
5084
+ traceId: exportedSpan.traceId,
5085
+ spanId: exportedSpan.id
5086
+ });
5087
+ await this._updateSpan({ span: exportedSpan, traceData });
5088
+ break;
5089
+ case "handleSpanEnd":
5090
+ this.logger.debug(`${this.name}: handling span end`, {
5091
+ traceId: exportedSpan.traceId,
5092
+ spanId: exportedSpan.id
5093
+ });
5094
+ traceData.endSpan({ spanId: exportedSpan.id });
5095
+ await this._finishSpan({ span: exportedSpan, traceData });
5096
+ if (traceData.activeSpanCount() === 0) {
5097
+ this.#scheduleCleanup(traceId);
5098
+ }
5099
+ break;
5100
+ }
5101
+ } catch (error) {
5102
+ this.logger.error(`${this.name}: exporter error`, { error, event, method });
5103
+ }
5104
+ if (traceData.activeSpanCount() === 0) {
5105
+ this.#scheduleCleanup(traceId);
5106
+ }
5107
+ await this._postExportTracingEvent();
5108
+ }
5109
+ // ============================================================================
5110
+ // Protected Helpers
5111
+ // ============================================================================
5112
+ /**
5113
+ * Get or create the TraceData container for a trace.
5114
+ * Also cancels any pending cleanup since new data has arrived.
5115
+ *
5116
+ * @param args.traceId - The trace ID
5117
+ * @param args.method - The calling method name (for logging)
5118
+ * @returns The TraceData container for this trace
5119
+ */
5120
+ getTraceData(args) {
5121
+ const { traceId, method } = args;
5122
+ this.#cancelScheduledCleanup(traceId);
5123
+ if (!this.#traceMap.has(traceId)) {
5124
+ this.#traceMap.set(traceId, new TraceData());
5125
+ this.logger.debug(`${this.name}: Created new trace data cache`, {
5126
+ traceId,
5127
+ method
5128
+ });
5129
+ this.#enforceHardCap().catch((error) => {
5130
+ this.logger.error(`${this.name}: Error enforcing hard cap`, { error });
5131
+ });
5132
+ }
5133
+ return this.#traceMap.get(traceId);
5134
+ }
5135
+ /**
5136
+ * Get the current number of traces being tracked.
5137
+ * @returns The trace count
5138
+ */
5139
+ traceMapSize() {
5140
+ return this.#traceMap.size;
5141
+ }
5142
+ // ============================================================================
5143
+ // Shutdown Hooks (Override in subclass as needed)
5144
+ // ============================================================================
5145
+ /**
5146
+ * Hook called at the start of shutdown, before cancelling timers and aborting spans.
5147
+ * Override to perform vendor-specific pre-shutdown tasks.
5148
+ */
5149
+ async _preShutdown() {
5150
+ }
5151
+ /**
5152
+ * Hook called at the end of shutdown, after all spans are aborted.
5153
+ * Override to perform vendor-specific cleanup (e.g., flushing).
5154
+ */
5155
+ async _postShutdown() {
5156
+ }
5157
+ /**
5158
+ * Gracefully shut down the exporter.
5159
+ * Cancels all pending cleanup timers, aborts all active spans, and clears state.
5160
+ */
5161
+ async shutdown() {
5162
+ if (this.isDisabled) {
5163
+ return;
5164
+ }
5165
+ this.#shutdownStarted = true;
5166
+ await this._preShutdown();
5167
+ for (const [traceId, timeout] of this.#pendingCleanups) {
5168
+ clearTimeout(timeout);
5169
+ this.logger.debug(`${this.name}: Cancelled pending cleanup on shutdown`, { traceId });
5170
+ }
5171
+ this.#pendingCleanups.clear();
5172
+ const reason = {
5173
+ id: "SHUTDOWN",
5174
+ message: "Observability is shutting down.",
5175
+ domain: "MASTRA_OBSERVABILITY",
5176
+ category: "SYSTEM"
5177
+ };
5178
+ for (const [traceId, traceData] of this.#traceMap) {
5179
+ const orphanedEvents = traceData.getAllQueuedEvents();
5180
+ if (orphanedEvents.length > 0) {
5181
+ this.logger.warn(`${this.name}: Dropping ${orphanedEvents.length} orphaned events on shutdown`, {
5182
+ traceId,
5183
+ orphanedEvents: orphanedEvents.map((e) => ({
5184
+ spanId: e.event.exportedSpan.id,
5185
+ waitingFor: e.waitingFor,
5186
+ attempts: e.attempts
5187
+ }))
5188
+ });
5189
+ }
5190
+ for (const spanId of traceData.activeSpanIds) {
5191
+ const span = traceData.getSpan({ spanId });
5192
+ if (span) {
5193
+ await this._abortSpan({ span, traceData, reason });
5194
+ }
5195
+ }
5196
+ }
5197
+ this.#traceMap.clear();
5198
+ await this._postShutdown();
5199
+ await super.shutdown();
5200
+ }
5201
+ };
5202
+
5203
+ // src/exporters/span-formatters.ts
5204
+ function chainFormatters(formatters) {
5205
+ return async (span) => {
5206
+ let currentSpan = span;
5207
+ for (const formatter of formatters) {
5208
+ currentSpan = await formatter(currentSpan);
5209
+ }
5210
+ return currentSpan;
5211
+ };
5212
+ }
4228
5213
  var CloudExporter = class extends BaseExporter {
4229
5214
  name = "mastra-cloud-observability-exporter";
4230
- config;
5215
+ cloudConfig;
4231
5216
  buffer;
4232
5217
  flushTimer = null;
4233
5218
  constructor(config = {}) {
@@ -4237,7 +5222,7 @@ var CloudExporter = class extends BaseExporter {
4237
5222
  this.setDisabled("MASTRA_CLOUD_ACCESS_TOKEN environment variable not set.");
4238
5223
  }
4239
5224
  const endpoint = config.endpoint ?? process.env.MASTRA_CLOUD_TRACES_ENDPOINT ?? "https://api.mastra.ai/ai/spans/publish";
4240
- this.config = {
5225
+ this.cloudConfig = {
4241
5226
  logger: this.logger,
4242
5227
  logLevel: config.logLevel ?? LogLevel.INFO,
4243
5228
  maxBatchSize: config.maxBatchSize ?? 1e3,
@@ -4295,12 +5280,12 @@ var CloudExporter = class extends BaseExporter {
4295
5280
  return spanRecord;
4296
5281
  }
4297
5282
  shouldFlush() {
4298
- if (this.buffer.totalSize >= this.config.maxBatchSize) {
5283
+ if (this.buffer.totalSize >= this.cloudConfig.maxBatchSize) {
4299
5284
  return true;
4300
5285
  }
4301
5286
  if (this.buffer.firstEventTime && this.buffer.totalSize > 0) {
4302
5287
  const elapsed = Date.now() - this.buffer.firstEventTime.getTime();
4303
- if (elapsed >= this.config.maxBatchWaitMs) {
5288
+ if (elapsed >= this.cloudConfig.maxBatchWaitMs) {
4304
5289
  return true;
4305
5290
  }
4306
5291
  }
@@ -4323,7 +5308,7 @@ var CloudExporter = class extends BaseExporter {
4323
5308
  this.logger.trackException(mastraError);
4324
5309
  this.logger.error("Scheduled flush failed", mastraError);
4325
5310
  });
4326
- }, this.config.maxBatchWaitMs);
5311
+ }, this.cloudConfig.maxBatchWaitMs);
4327
5312
  }
4328
5313
  async flush() {
4329
5314
  if (this.flushTimer) {
@@ -4335,7 +5320,7 @@ var CloudExporter = class extends BaseExporter {
4335
5320
  }
4336
5321
  const startTime = Date.now();
4337
5322
  const spansCopy = [...this.buffer.spans];
4338
- const flushReason = this.buffer.totalSize >= this.config.maxBatchSize ? "size" : "time";
5323
+ const flushReason = this.buffer.totalSize >= this.cloudConfig.maxBatchSize ? "size" : "time";
4339
5324
  this.resetBuffer();
4340
5325
  try {
4341
5326
  await this.batchUpload(spansCopy);
@@ -4366,7 +5351,7 @@ var CloudExporter = class extends BaseExporter {
4366
5351
  */
4367
5352
  async batchUpload(spans) {
4368
5353
  const headers = {
4369
- Authorization: `Bearer ${this.config.accessToken}`,
5354
+ Authorization: `Bearer ${this.cloudConfig.accessToken}`,
4370
5355
  "Content-Type": "application/json"
4371
5356
  };
4372
5357
  const options = {
@@ -4374,7 +5359,7 @@ var CloudExporter = class extends BaseExporter {
4374
5359
  headers,
4375
5360
  body: JSON.stringify({ spans })
4376
5361
  };
4377
- await fetchWithRetry(this.config.endpoint, options, this.config.maxRetries);
5362
+ await fetchWithRetry(this.cloudConfig.endpoint, options, this.cloudConfig.maxRetries);
4378
5363
  }
4379
5364
  resetBuffer() {
4380
5365
  this.buffer.spans = [];
@@ -5478,10 +6463,39 @@ var ModelSpanTracker = class {
5478
6463
  case "step-finish":
5479
6464
  this.#endStepSpan(chunk.payload);
5480
6465
  break;
6466
+ // Infrastructure chunks - skip creating spans for these
6467
+ // They are either redundant, metadata-only, or error/control flow
5481
6468
  case "raw":
5482
- // Skip raw chunks as they're redundant
6469
+ // Redundant raw data
5483
6470
  case "start":
6471
+ // Stream start marker
5484
6472
  case "finish":
6473
+ // Stream finish marker (step-finish already captures this)
6474
+ case "response-metadata":
6475
+ // Response metadata (not semantic content)
6476
+ case "source":
6477
+ // Source references (metadata)
6478
+ case "file":
6479
+ // Binary file data (too large/not semantic)
6480
+ case "error":
6481
+ // Error handling
6482
+ case "abort":
6483
+ // Abort signal
6484
+ case "tripwire":
6485
+ // Processor rejection
6486
+ case "watch":
6487
+ // Internal watch event
6488
+ case "tool-error":
6489
+ // Tool error handling
6490
+ case "tool-call-approval":
6491
+ // Approval request (not content)
6492
+ case "tool-call-suspended":
6493
+ // Suspension (not content)
6494
+ case "reasoning-signature":
6495
+ // Signature metadata
6496
+ case "redacted-reasoning":
6497
+ // Redacted content metadata
6498
+ case "step-output":
5485
6499
  break;
5486
6500
  case "tool-output":
5487
6501
  this.#handleToolOutputChunk(chunk);
@@ -5496,20 +6510,6 @@ var ModelSpanTracker = class {
5496
6510
  this.#createEventSpan(chunk.type, cleanPayload);
5497
6511
  break;
5498
6512
  }
5499
- // Default: auto-create event span for all other chunk types
5500
- default: {
5501
- let outputPayload = chunk.payload;
5502
- if (outputPayload && typeof outputPayload === "object" && "data" in outputPayload) {
5503
- const typedPayload = outputPayload;
5504
- outputPayload = { ...typedPayload };
5505
- if (typedPayload.data) {
5506
- outputPayload.size = typeof typedPayload.data === "string" ? typedPayload.data.length : typedPayload.data instanceof Uint8Array ? typedPayload.data.length : void 0;
5507
- delete outputPayload.data;
5508
- }
5509
- }
5510
- this.#createEventSpan(chunk.type, outputPayload);
5511
- break;
5512
- }
5513
6513
  }
5514
6514
  }
5515
6515
  })
@@ -5710,15 +6710,15 @@ var BaseSpan = class {
5710
6710
  this.attributes = deepClean(options.attributes, this.deepCleanOptions) || {};
5711
6711
  this.metadata = deepClean(options.metadata, this.deepCleanOptions);
5712
6712
  this.parent = options.parent;
5713
- this.startTime = /* @__PURE__ */ new Date();
6713
+ this.startTime = options.startTime ?? /* @__PURE__ */ new Date();
5714
6714
  this.observabilityInstance = observabilityInstance;
5715
6715
  this.isEvent = options.isEvent ?? false;
5716
6716
  this.isInternal = isSpanInternal(this.type, options.tracingPolicy?.internal);
5717
6717
  this.traceState = options.traceState;
5718
6718
  this.tags = !options.parent && options.tags?.length ? options.tags : void 0;
5719
- this.entityType = options.entityType;
5720
- this.entityId = options.entityId;
5721
- this.entityName = options.entityName;
6719
+ this.entityType = options.entityType ?? options.parent?.entityType;
6720
+ this.entityId = options.entityId ?? options.parent?.entityId;
6721
+ this.entityName = options.entityName ?? options.parent?.entityName;
5722
6722
  if (this.isEvent) {
5723
6723
  this.output = deepClean(options.output, this.deepCleanOptions);
5724
6724
  } else {
@@ -5767,6 +6767,8 @@ var BaseSpan = class {
5767
6767
  }
5768
6768
  /** Returns a lightweight span ready for export */
5769
6769
  exportSpan(includeInternalSpans) {
6770
+ const hideInput = this.traceState?.hideInput ?? false;
6771
+ const hideOutput = this.traceState?.hideOutput ?? false;
5770
6772
  return {
5771
6773
  id: this.id,
5772
6774
  traceId: this.traceId,
@@ -5779,8 +6781,8 @@ var BaseSpan = class {
5779
6781
  metadata: this.metadata,
5780
6782
  startTime: this.startTime,
5781
6783
  endTime: this.endTime,
5782
- input: this.input,
5783
- output: this.output,
6784
+ input: hideInput ? void 0 : this.input,
6785
+ output: hideOutput ? void 0 : this.output,
5784
6786
  errorInfo: this.errorInfo,
5785
6787
  isEvent: this.isEvent,
5786
6788
  isRootSpan: this.isRootSpan,
@@ -5820,6 +6822,14 @@ var DefaultSpan = class extends BaseSpan {
5820
6822
  traceId;
5821
6823
  constructor(options, observabilityInstance) {
5822
6824
  super(options, observabilityInstance);
6825
+ if (options.spanId && options.traceId) {
6826
+ this.id = options.spanId;
6827
+ this.traceId = options.traceId;
6828
+ if (options.parentSpanId) {
6829
+ this.parentSpanId = options.parentSpanId;
6830
+ }
6831
+ return;
6832
+ }
5823
6833
  const bridge = observabilityInstance.getBridge();
5824
6834
  if (bridge && !this.isInternal) {
5825
6835
  const bridgeIds = bridge.createSpan(options);
@@ -6064,8 +7074,12 @@ var BaseObservabilityInstance = class extends MastraBase {
6064
7074
  const mergedMetadata = metadata || tracingMetadata ? { ...metadata, ...tracingMetadata } : void 0;
6065
7075
  const enrichedMetadata = this.extractMetadataFromRequestContext(requestContext, mergedMetadata, traceState);
6066
7076
  const tags = !options.parent ? tracingOptions?.tags : void 0;
7077
+ const traceId = !options.parent ? options.traceId ?? tracingOptions?.traceId : options.traceId;
7078
+ const parentSpanId = !options.parent ? options.parentSpanId ?? tracingOptions?.parentSpanId : options.parentSpanId;
6067
7079
  const span = this.createSpan({
6068
7080
  ...rest,
7081
+ traceId,
7082
+ parentSpanId,
6069
7083
  metadata: enrichedMetadata,
6070
7084
  traceState,
6071
7085
  tags
@@ -6078,6 +7092,37 @@ var BaseObservabilityInstance = class extends MastraBase {
6078
7092
  }
6079
7093
  return span;
6080
7094
  }
7095
+ /**
7096
+ * Rebuild a span from exported data for lifecycle operations.
7097
+ * Used by durable execution engines (e.g., Inngest) to end/update spans
7098
+ * that were created in a previous durable operation.
7099
+ *
7100
+ * The rebuilt span:
7101
+ * - Does NOT emit SPAN_STARTED (assumes original span already did)
7102
+ * - Can have end(), update(), error() called on it
7103
+ * - Will emit SPAN_ENDED or SPAN_UPDATED when those methods are called
7104
+ *
7105
+ * @param cached - The exported span data to rebuild from
7106
+ * @returns A span that can have lifecycle methods called on it
7107
+ */
7108
+ rebuildSpan(cached) {
7109
+ const span = this.createSpan({
7110
+ name: cached.name,
7111
+ type: cached.type,
7112
+ traceId: cached.traceId,
7113
+ spanId: cached.id,
7114
+ parentSpanId: cached.parentSpanId,
7115
+ startTime: cached.startTime instanceof Date ? cached.startTime : new Date(cached.startTime),
7116
+ input: cached.input,
7117
+ attributes: cached.attributes,
7118
+ metadata: cached.metadata,
7119
+ entityType: cached.entityType,
7120
+ entityId: cached.entityId,
7121
+ entityName: cached.entityName
7122
+ });
7123
+ this.wireSpanLifecycle(span);
7124
+ return span;
7125
+ }
6081
7126
  // ============================================================================
6082
7127
  // Configuration Management
6083
7128
  // ============================================================================
@@ -6180,11 +7225,15 @@ var BaseObservabilityInstance = class extends MastraBase {
6180
7225
  const configuredKeys = this.config.requestContextKeys ?? [];
6181
7226
  const additionalKeys = tracingOptions?.requestContextKeys ?? [];
6182
7227
  const allKeys = [...configuredKeys, ...additionalKeys];
6183
- if (allKeys.length === 0) {
7228
+ const hideInput = tracingOptions?.hideInput;
7229
+ const hideOutput = tracingOptions?.hideOutput;
7230
+ if (allKeys.length === 0 && !hideInput && !hideOutput) {
6184
7231
  return void 0;
6185
7232
  }
6186
7233
  return {
6187
- requestContextKeys: allKeys
7234
+ requestContextKeys: allKeys,
7235
+ ...hideInput !== void 0 && { hideInput },
7236
+ ...hideOutput !== void 0 && { hideOutput }
6188
7237
  };
6189
7238
  }
6190
7239
  /**
@@ -6714,6 +7763,6 @@ function buildTracingOptions(...updaters) {
6714
7763
  return updaters.reduce((opts, updater) => updater(opts), {});
6715
7764
  }
6716
7765
 
6717
- export { BaseExporter, BaseObservabilityInstance, BaseSpan, CloudExporter, ConsoleExporter, DEFAULT_DEEP_CLEAN_OPTIONS, DEFAULT_KEYS_TO_STRIP, DefaultExporter, DefaultObservabilityInstance, DefaultSpan, ModelSpanTracker, NoOpSpan, Observability, SamplingStrategyType, SensitiveDataFilter, TestExporter, buildTracingOptions, deepClean, getExternalParentId, mergeSerializationOptions, observabilityConfigValueSchema, observabilityInstanceConfigSchema, observabilityRegistryConfigSchema, samplingStrategySchema, serializationOptionsSchema, truncateString };
7766
+ export { BaseExporter, BaseObservabilityInstance, BaseSpan, CloudExporter, ConsoleExporter, DEFAULT_DEEP_CLEAN_OPTIONS, DEFAULT_KEYS_TO_STRIP, DefaultExporter, DefaultObservabilityInstance, DefaultSpan, ModelSpanTracker, NoOpSpan, Observability, SamplingStrategyType, SensitiveDataFilter, TestExporter, TraceData, TrackingExporter, buildTracingOptions, chainFormatters, deepClean, getExternalParentId, mergeSerializationOptions, observabilityConfigValueSchema, observabilityInstanceConfigSchema, observabilityRegistryConfigSchema, samplingStrategySchema, serializationOptionsSchema, truncateString };
6718
7767
  //# sourceMappingURL=index.js.map
6719
7768
  //# sourceMappingURL=index.js.map