@mastra/observability 0.0.0-remove-ai-peer-dep-from-evals-20260105220639 → 0.0.0-salesman-20260127182805

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,62 @@ 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);
4255
+ }
4256
+ /**
4257
+ * Force flush any buffered/queued spans without shutting down the exporter.
4258
+ *
4259
+ * This is useful in serverless environments where you need to ensure spans
4260
+ * are exported before the runtime instance is terminated, while keeping
4261
+ * the exporter active for future requests.
4262
+ *
4263
+ * Default implementation is a no-op. Override to add flush logic.
4264
+ */
4265
+ async flush() {
4266
+ this.logger.debug(`${this.name} flush called (no-op in base class)`);
4218
4267
  }
4219
4268
  /**
4220
4269
  * Shutdown the exporter and clean up resources
@@ -4225,21 +4274,990 @@ var BaseExporter = class {
4225
4274
  this.logger.info(`${this.name} shutdown complete`);
4226
4275
  }
4227
4276
  };
4277
+ var TraceData = class {
4278
+ /** The vendor-specific root/trace object */
4279
+ #rootSpan;
4280
+ /** The span ID of the root span */
4281
+ #rootSpanId;
4282
+ /** Whether a span with isRootSpan=true has been successfully processed */
4283
+ #rootSpanProcessed;
4284
+ /** Maps eventId to vendor-specific event objects */
4285
+ #events;
4286
+ /** Maps spanId to vendor-specific span objects */
4287
+ #spans;
4288
+ /** Maps spanId to parentSpanId, representing the span hierarchy */
4289
+ #tree;
4290
+ /** Set of span IDs that have started but not yet ended */
4291
+ #activeSpanIds;
4292
+ /** Maps spanId to vendor-specific metadata */
4293
+ #metadata;
4294
+ /** Arbitrary key-value storage for per-trace data */
4295
+ #extraData;
4296
+ /** Events waiting for the root span to be processed */
4297
+ #waitingForRoot;
4298
+ /** Events waiting for specific parent spans, keyed by parentSpanId */
4299
+ #waitingForParent;
4300
+ /** When this trace data was created, used for cap enforcement */
4301
+ createdAt;
4302
+ constructor() {
4303
+ this.#events = /* @__PURE__ */ new Map();
4304
+ this.#spans = /* @__PURE__ */ new Map();
4305
+ this.#activeSpanIds = /* @__PURE__ */ new Set();
4306
+ this.#tree = /* @__PURE__ */ new Map();
4307
+ this.#metadata = /* @__PURE__ */ new Map();
4308
+ this.#extraData = /* @__PURE__ */ new Map();
4309
+ this.#rootSpanProcessed = false;
4310
+ this.#waitingForRoot = [];
4311
+ this.#waitingForParent = /* @__PURE__ */ new Map();
4312
+ this.createdAt = /* @__PURE__ */ new Date();
4313
+ }
4314
+ /**
4315
+ * Check if this trace has a root span registered.
4316
+ * @returns True if addRoot() has been called
4317
+ */
4318
+ hasRoot() {
4319
+ return !!this.#rootSpanId;
4320
+ }
4321
+ /**
4322
+ * Register the root span for this trace.
4323
+ * @param args.rootId - The span ID of the root span
4324
+ * @param args.rootData - The vendor-specific root object
4325
+ */
4326
+ addRoot(args) {
4327
+ this.#rootSpanId = args.rootId;
4328
+ this.#rootSpan = args.rootData;
4329
+ this.#rootSpanProcessed = true;
4330
+ }
4331
+ /**
4332
+ * Get the vendor-specific root object.
4333
+ * @returns The root object, or undefined if not yet set
4334
+ */
4335
+ getRoot() {
4336
+ return this.#rootSpan;
4337
+ }
4338
+ /**
4339
+ * Check if a span with isRootSpan=true has been successfully processed.
4340
+ * Set via addRoot() or markRootSpanProcessed().
4341
+ * @returns True if the root span has been processed
4342
+ */
4343
+ isRootProcessed() {
4344
+ return this.#rootSpanProcessed;
4345
+ }
4346
+ /**
4347
+ * Mark that the root span has been processed.
4348
+ * Used by exporters with skipBuildRootTask=true where root goes through _buildSpan
4349
+ * instead of _buildRoot.
4350
+ */
4351
+ markRootSpanProcessed() {
4352
+ this.#rootSpanProcessed = true;
4353
+ }
4354
+ /**
4355
+ * Store an arbitrary value in per-trace storage.
4356
+ * @param key - Storage key
4357
+ * @param value - Value to store
4358
+ */
4359
+ setExtraValue(key, value) {
4360
+ this.#extraData.set(key, value);
4361
+ }
4362
+ /**
4363
+ * Check if a key exists in per-trace storage.
4364
+ * @param key - Storage key
4365
+ * @returns True if the key exists
4366
+ */
4367
+ hasExtraValue(key) {
4368
+ return this.#extraData.has(key);
4369
+ }
4370
+ /**
4371
+ * Get a value from per-trace storage.
4372
+ * @param key - Storage key
4373
+ * @returns The stored value, or undefined if not found
4374
+ */
4375
+ getExtraValue(key) {
4376
+ return this.#extraData.get(key);
4377
+ }
4378
+ // ============================================================================
4379
+ // Early Queue Methods
4380
+ // ============================================================================
4381
+ /**
4382
+ * Add an event to the waiting queue.
4383
+ * @param args.event - The tracing event to queue
4384
+ * @param args.waitingFor - 'root' or a specific parentSpanId
4385
+ * @param args.attempts - Optional: preserve attempts count when re-queuing
4386
+ * @param args.queuedAt - Optional: preserve original queue time when re-queuing
4387
+ */
4388
+ addToWaitingQueue(args) {
4389
+ const queuedEvent = {
4390
+ event: args.event,
4391
+ waitingFor: args.waitingFor,
4392
+ attempts: args.attempts ?? 0,
4393
+ queuedAt: args.queuedAt ?? /* @__PURE__ */ new Date()
4394
+ };
4395
+ if (args.waitingFor === "root") {
4396
+ this.#waitingForRoot.push(queuedEvent);
4397
+ } else {
4398
+ const queue = this.#waitingForParent.get(args.waitingFor) ?? [];
4399
+ queue.push(queuedEvent);
4400
+ this.#waitingForParent.set(args.waitingFor, queue);
4401
+ }
4402
+ }
4403
+ /**
4404
+ * Get all events waiting for the root span.
4405
+ * Returns a copy of the internal array.
4406
+ */
4407
+ getEventsWaitingForRoot() {
4408
+ return [...this.#waitingForRoot];
4409
+ }
4410
+ /**
4411
+ * Get all events waiting for a specific parent span.
4412
+ * Returns a copy of the internal array.
4413
+ */
4414
+ getEventsWaitingFor(args) {
4415
+ return [...this.#waitingForParent.get(args.spanId) ?? []];
4416
+ }
4417
+ /**
4418
+ * Clear the waiting-for-root queue.
4419
+ */
4420
+ clearWaitingForRoot() {
4421
+ this.#waitingForRoot = [];
4422
+ }
4423
+ /**
4424
+ * Clear the waiting queue for a specific parent span.
4425
+ */
4426
+ clearWaitingFor(args) {
4427
+ this.#waitingForParent.delete(args.spanId);
4428
+ }
4429
+ /**
4430
+ * Get total count of events in all waiting queues.
4431
+ */
4432
+ waitingQueueSize() {
4433
+ let count = this.#waitingForRoot.length;
4434
+ for (const queue of this.#waitingForParent.values()) {
4435
+ count += queue.length;
4436
+ }
4437
+ return count;
4438
+ }
4439
+ /**
4440
+ * Get all queued events across all waiting queues.
4441
+ * Used for cleanup and logging orphaned events.
4442
+ * @returns Array of all queued events
4443
+ */
4444
+ getAllQueuedEvents() {
4445
+ const all = [...this.#waitingForRoot];
4446
+ for (const queue of this.#waitingForParent.values()) {
4447
+ all.push(...queue);
4448
+ }
4449
+ return all;
4450
+ }
4451
+ // ============================================================================
4452
+ // Span Tree Methods
4453
+ // ============================================================================
4454
+ /**
4455
+ * Record the parent-child relationship for a span.
4456
+ * @param args.spanId - The child span ID
4457
+ * @param args.parentSpanId - The parent span ID, or undefined for root spans
4458
+ */
4459
+ addBranch(args) {
4460
+ this.#tree.set(args.spanId, args.parentSpanId);
4461
+ }
4462
+ /**
4463
+ * Get the parent span ID for a given span.
4464
+ * @param args.spanId - The span ID to look up
4465
+ * @returns The parent span ID, or undefined if root or not found
4466
+ */
4467
+ getParentId(args) {
4468
+ return this.#tree.get(args.spanId);
4469
+ }
4470
+ // ============================================================================
4471
+ // Span Management Methods
4472
+ // ============================================================================
4473
+ /**
4474
+ * Register a span and mark it as active.
4475
+ * @param args.spanId - The span ID
4476
+ * @param args.spanData - The vendor-specific span object
4477
+ */
4478
+ addSpan(args) {
4479
+ this.#spans.set(args.spanId, args.spanData);
4480
+ this.#activeSpanIds.add(args.spanId);
4481
+ }
4482
+ /**
4483
+ * Check if a span exists (regardless of active state).
4484
+ * @param args.spanId - The span ID to check
4485
+ * @returns True if the span exists
4486
+ */
4487
+ hasSpan(args) {
4488
+ return this.#spans.has(args.spanId);
4489
+ }
4490
+ /**
4491
+ * Get a span by ID.
4492
+ * @param args.spanId - The span ID to look up
4493
+ * @returns The vendor-specific span object, or undefined if not found
4494
+ */
4495
+ getSpan(args) {
4496
+ return this.#spans.get(args.spanId);
4497
+ }
4498
+ /**
4499
+ * Mark a span as ended (no longer active).
4500
+ * @param args.spanId - The span ID to mark as ended
4501
+ */
4502
+ endSpan(args) {
4503
+ this.#activeSpanIds.delete(args.spanId);
4504
+ }
4505
+ /**
4506
+ * Check if a span is currently active (started but not ended).
4507
+ * @param args.spanId - The span ID to check
4508
+ * @returns True if the span is active
4509
+ */
4510
+ isActiveSpan(args) {
4511
+ return this.#activeSpanIds.has(args.spanId);
4512
+ }
4513
+ /**
4514
+ * Get the count of currently active spans.
4515
+ * @returns Number of active spans
4516
+ */
4517
+ activeSpanCount() {
4518
+ return this.#activeSpanIds.size;
4519
+ }
4520
+ /**
4521
+ * Get all active span IDs.
4522
+ * @returns Array of active span IDs
4523
+ */
4524
+ get activeSpanIds() {
4525
+ return [...this.#activeSpanIds];
4526
+ }
4527
+ // ============================================================================
4528
+ // Event Management Methods
4529
+ // ============================================================================
4530
+ /**
4531
+ * Register an event.
4532
+ * @param args.eventId - The event ID
4533
+ * @param args.eventData - The vendor-specific event object
4534
+ */
4535
+ addEvent(args) {
4536
+ this.#events.set(args.eventId, args.eventData);
4537
+ }
4538
+ // ============================================================================
4539
+ // Metadata Methods
4540
+ // ============================================================================
4541
+ /**
4542
+ * Store vendor-specific metadata for a span.
4543
+ * Note: This overwrites any existing metadata for the span.
4544
+ * @param args.spanId - The span ID
4545
+ * @param args.metadata - The vendor-specific metadata
4546
+ */
4547
+ addMetadata(args) {
4548
+ this.#metadata.set(args.spanId, args.metadata);
4549
+ }
4550
+ /**
4551
+ * Get vendor-specific metadata for a span.
4552
+ * @param args.spanId - The span ID
4553
+ * @returns The metadata, or undefined if not found
4554
+ */
4555
+ getMetadata(args) {
4556
+ return this.#metadata.get(args.spanId);
4557
+ }
4558
+ // ============================================================================
4559
+ // Parent Lookup Methods
4560
+ // ============================================================================
4561
+ /**
4562
+ * Get the parent span or event for a given span.
4563
+ * Looks up in both spans and events maps.
4564
+ * @param args.span - The span to find the parent for
4565
+ * @returns The parent span/event object, or undefined if root or not found
4566
+ */
4567
+ getParent(args) {
4568
+ const parentId = args.span.parentSpanId;
4569
+ if (parentId) {
4570
+ if (this.#spans.has(parentId)) {
4571
+ return this.#spans.get(parentId);
4572
+ }
4573
+ if (this.#events.has(parentId)) {
4574
+ return this.#events.get(parentId);
4575
+ }
4576
+ }
4577
+ return void 0;
4578
+ }
4579
+ /**
4580
+ * Get the parent span/event or fall back to the root object.
4581
+ * Useful for vendors that attach child spans to either parent spans or the trace root.
4582
+ * @param args.span - The span to find the parent for
4583
+ * @returns The parent span/event, the root object, or undefined
4584
+ */
4585
+ getParentOrRoot(args) {
4586
+ return this.getParent(args) ?? this.getRoot();
4587
+ }
4588
+ };
4589
+ var DEFAULT_EARLY_QUEUE_MAX_ATTEMPTS = 5;
4590
+ var DEFAULT_EARLY_QUEUE_TTL_MS = 3e4;
4591
+ var DEFAULT_TRACE_CLEANUP_DELAY_MS = 3e4;
4592
+ var DEFAULT_MAX_PENDING_CLEANUP_TRACES = 100;
4593
+ var DEFAULT_MAX_TOTAL_TRACES = 500;
4594
+ var TrackingExporter = class extends BaseExporter {
4595
+ /** Map of traceId to per-trace data container */
4596
+ #traceMap = /* @__PURE__ */ new Map();
4597
+ /** Flag to prevent processing during shutdown */
4598
+ #shutdownStarted = false;
4599
+ /** Flag to prevent concurrent hard cap enforcement */
4600
+ #hardCapEnforcementInProgress = false;
4601
+ /** Map of traceId to scheduled cleanup timeout */
4602
+ #pendingCleanups = /* @__PURE__ */ new Map();
4603
+ // Note: #traceMap maintains insertion order (JS Map spec), so we use
4604
+ // #traceMap.keys() to iterate traces oldest-first for cap enforcement.
4605
+ /** Subclass configuration with resolved values */
4606
+ config;
4607
+ /** Maximum attempts to process a queued event before dropping */
4608
+ #earlyQueueMaxAttempts;
4609
+ /** TTL in milliseconds for queued events */
4610
+ #earlyQueueTTLMs;
4611
+ /** Delay before cleaning up completed traces */
4612
+ #traceCleanupDelayMs;
4613
+ /** Soft cap on traces awaiting cleanup */
4614
+ #maxPendingCleanupTraces;
4615
+ /** Hard cap on total traces (will abort active spans if exceeded) */
4616
+ #maxTotalTraces;
4617
+ constructor(config) {
4618
+ super(config);
4619
+ this.config = config;
4620
+ this.#earlyQueueMaxAttempts = config.earlyQueueMaxAttempts ?? DEFAULT_EARLY_QUEUE_MAX_ATTEMPTS;
4621
+ this.#earlyQueueTTLMs = config.earlyQueueTTLMs ?? DEFAULT_EARLY_QUEUE_TTL_MS;
4622
+ this.#traceCleanupDelayMs = config.traceCleanupDelayMs ?? DEFAULT_TRACE_CLEANUP_DELAY_MS;
4623
+ this.#maxPendingCleanupTraces = config.maxPendingCleanupTraces ?? DEFAULT_MAX_PENDING_CLEANUP_TRACES;
4624
+ this.#maxTotalTraces = config.maxTotalTraces ?? DEFAULT_MAX_TOTAL_TRACES;
4625
+ }
4626
+ // ============================================================================
4627
+ // Early Queue Processing
4628
+ // ============================================================================
4629
+ /**
4630
+ * Schedule async processing of events waiting for root span.
4631
+ * Called after root span is successfully processed.
4632
+ */
4633
+ #scheduleProcessWaitingForRoot(traceId) {
4634
+ setImmediate(() => {
4635
+ this.#processWaitingForRoot(traceId).catch((error) => {
4636
+ this.logger.error(`${this.name}: Error processing waiting-for-root queue`, { error, traceId });
4637
+ });
4638
+ });
4639
+ }
4640
+ /**
4641
+ * Schedule async processing of events waiting for a specific parent span.
4642
+ * Called after a span/event is successfully created.
4643
+ */
4644
+ #scheduleProcessWaitingFor(traceId, spanId) {
4645
+ setImmediate(() => {
4646
+ this.#processWaitingFor(traceId, spanId).catch((error) => {
4647
+ this.logger.error(`${this.name}: Error processing waiting queue`, { error, traceId, spanId });
4648
+ });
4649
+ });
4650
+ }
4651
+ /**
4652
+ * Process all events waiting for root span.
4653
+ */
4654
+ async #processWaitingForRoot(traceId) {
4655
+ if (this.#shutdownStarted) return;
4656
+ const traceData = this.#traceMap.get(traceId);
4657
+ if (!traceData) return;
4658
+ const queue = traceData.getEventsWaitingForRoot();
4659
+ if (queue.length === 0) return;
4660
+ this.logger.debug(`${this.name}: Processing ${queue.length} events waiting for root`, { traceId });
4661
+ const toKeep = [];
4662
+ const now = Date.now();
4663
+ for (const queuedEvent of queue) {
4664
+ if (now - queuedEvent.queuedAt.getTime() > this.#earlyQueueTTLMs) {
4665
+ this.logger.warn(`${this.name}: Dropping event due to TTL expiry`, {
4666
+ traceId,
4667
+ spanId: queuedEvent.event.exportedSpan.id,
4668
+ waitingFor: queuedEvent.waitingFor,
4669
+ queuedAt: queuedEvent.queuedAt,
4670
+ attempts: queuedEvent.attempts
4671
+ });
4672
+ continue;
4673
+ }
4674
+ if (queuedEvent.attempts >= this.#earlyQueueMaxAttempts) {
4675
+ this.logger.warn(`${this.name}: Dropping event due to max attempts`, {
4676
+ traceId,
4677
+ spanId: queuedEvent.event.exportedSpan.id,
4678
+ waitingFor: queuedEvent.waitingFor,
4679
+ attempts: queuedEvent.attempts
4680
+ });
4681
+ continue;
4682
+ }
4683
+ queuedEvent.attempts++;
4684
+ const processed = await this.#tryProcessQueuedEvent(queuedEvent, traceData);
4685
+ if (!processed) {
4686
+ const parentId = queuedEvent.event.exportedSpan.parentSpanId;
4687
+ if (parentId && traceData.isRootProcessed()) {
4688
+ traceData.addToWaitingQueue({
4689
+ event: queuedEvent.event,
4690
+ waitingFor: parentId,
4691
+ attempts: queuedEvent.attempts,
4692
+ queuedAt: queuedEvent.queuedAt
4693
+ });
4694
+ } else {
4695
+ toKeep.push(queuedEvent);
4696
+ }
4697
+ }
4698
+ }
4699
+ traceData.clearWaitingForRoot();
4700
+ for (const event of toKeep) {
4701
+ traceData.addToWaitingQueue({
4702
+ event: event.event,
4703
+ waitingFor: "root",
4704
+ attempts: event.attempts,
4705
+ queuedAt: event.queuedAt
4706
+ });
4707
+ }
4708
+ }
4709
+ /**
4710
+ * Process events waiting for a specific parent span.
4711
+ */
4712
+ async #processWaitingFor(traceId, spanId) {
4713
+ if (this.#shutdownStarted) return;
4714
+ const traceData = this.#traceMap.get(traceId);
4715
+ if (!traceData) return;
4716
+ const queue = traceData.getEventsWaitingFor({ spanId });
4717
+ if (queue.length === 0) return;
4718
+ this.logger.debug(`${this.name}: Processing ${queue.length} events waiting for span`, { traceId, spanId });
4719
+ const toKeep = [];
4720
+ const now = Date.now();
4721
+ for (const queuedEvent of queue) {
4722
+ if (now - queuedEvent.queuedAt.getTime() > this.#earlyQueueTTLMs) {
4723
+ this.logger.warn(`${this.name}: Dropping event due to TTL expiry`, {
4724
+ traceId,
4725
+ spanId: queuedEvent.event.exportedSpan.id,
4726
+ waitingFor: queuedEvent.waitingFor,
4727
+ queuedAt: queuedEvent.queuedAt,
4728
+ attempts: queuedEvent.attempts
4729
+ });
4730
+ continue;
4731
+ }
4732
+ if (queuedEvent.attempts >= this.#earlyQueueMaxAttempts) {
4733
+ this.logger.warn(`${this.name}: Dropping event due to max attempts`, {
4734
+ traceId,
4735
+ spanId: queuedEvent.event.exportedSpan.id,
4736
+ waitingFor: queuedEvent.waitingFor,
4737
+ attempts: queuedEvent.attempts
4738
+ });
4739
+ continue;
4740
+ }
4741
+ queuedEvent.attempts++;
4742
+ const processed = await this.#tryProcessQueuedEvent(queuedEvent, traceData);
4743
+ if (!processed) {
4744
+ toKeep.push(queuedEvent);
4745
+ }
4746
+ }
4747
+ traceData.clearWaitingFor({ spanId });
4748
+ for (const event of toKeep) {
4749
+ traceData.addToWaitingQueue({
4750
+ event: event.event,
4751
+ waitingFor: spanId,
4752
+ attempts: event.attempts,
4753
+ queuedAt: event.queuedAt
4754
+ });
4755
+ }
4756
+ }
4757
+ /**
4758
+ * Try to process a queued event.
4759
+ * Returns true if successfully processed, false if still waiting for dependencies.
4760
+ */
4761
+ async #tryProcessQueuedEvent(queuedEvent, traceData) {
4762
+ const { event } = queuedEvent;
4763
+ const { exportedSpan } = event;
4764
+ const method = this.getMethod(event);
4765
+ try {
4766
+ switch (method) {
4767
+ case "handleEventSpan": {
4768
+ traceData.addBranch({ spanId: exportedSpan.id, parentSpanId: exportedSpan.parentSpanId });
4769
+ const eventData = await this._buildEvent({ span: exportedSpan, traceData });
4770
+ if (eventData) {
4771
+ if (!this.skipCachingEventSpans) {
4772
+ traceData.addEvent({ eventId: exportedSpan.id, eventData });
4773
+ }
4774
+ this.#scheduleProcessWaitingFor(exportedSpan.traceId, exportedSpan.id);
4775
+ return true;
4776
+ }
4777
+ return false;
4778
+ }
4779
+ case "handleSpanStart": {
4780
+ traceData.addBranch({ spanId: exportedSpan.id, parentSpanId: exportedSpan.parentSpanId });
4781
+ const spanData = await this._buildSpan({ span: exportedSpan, traceData });
4782
+ if (spanData) {
4783
+ traceData.addSpan({ spanId: exportedSpan.id, spanData });
4784
+ if (exportedSpan.isRootSpan) {
4785
+ traceData.markRootSpanProcessed();
4786
+ }
4787
+ this.#scheduleProcessWaitingFor(exportedSpan.traceId, exportedSpan.id);
4788
+ return true;
4789
+ }
4790
+ return false;
4791
+ }
4792
+ case "handleSpanUpdate": {
4793
+ await this._updateSpan({ span: exportedSpan, traceData });
4794
+ return true;
4795
+ }
4796
+ case "handleSpanEnd": {
4797
+ traceData.endSpan({ spanId: exportedSpan.id });
4798
+ await this._finishSpan({ span: exportedSpan, traceData });
4799
+ if (traceData.activeSpanCount() === 0) {
4800
+ this.#scheduleCleanup(exportedSpan.traceId);
4801
+ }
4802
+ return true;
4803
+ }
4804
+ default:
4805
+ return false;
4806
+ }
4807
+ } catch (error) {
4808
+ this.logger.error(`${this.name}: Error processing queued event`, { error, event, method });
4809
+ return false;
4810
+ }
4811
+ }
4812
+ // ============================================================================
4813
+ // Delayed Cleanup
4814
+ // ============================================================================
4815
+ /**
4816
+ * Schedule cleanup of trace data after a delay.
4817
+ * Allows late-arriving data to still be processed.
4818
+ */
4819
+ #scheduleCleanup(traceId) {
4820
+ this.#cancelScheduledCleanup(traceId);
4821
+ this.logger.debug(`${this.name}: Scheduling cleanup in ${this.#traceCleanupDelayMs}ms`, { traceId });
4822
+ const timeout = setTimeout(() => {
4823
+ this.#pendingCleanups.delete(traceId);
4824
+ this.#performCleanup(traceId);
4825
+ }, this.#traceCleanupDelayMs);
4826
+ this.#pendingCleanups.set(traceId, timeout);
4827
+ this.#enforcePendingCleanupCap();
4828
+ }
4829
+ /**
4830
+ * Cancel a scheduled cleanup for a trace.
4831
+ */
4832
+ #cancelScheduledCleanup(traceId) {
4833
+ const existingTimeout = this.#pendingCleanups.get(traceId);
4834
+ if (existingTimeout) {
4835
+ clearTimeout(existingTimeout);
4836
+ this.#pendingCleanups.delete(traceId);
4837
+ this.logger.debug(`${this.name}: Cancelled scheduled cleanup`, { traceId });
4838
+ }
4839
+ }
4840
+ /**
4841
+ * Perform the actual cleanup of trace data.
4842
+ */
4843
+ #performCleanup(traceId) {
4844
+ const traceData = this.#traceMap.get(traceId);
4845
+ if (!traceData) return;
4846
+ const orphanedEvents = traceData.getAllQueuedEvents();
4847
+ if (orphanedEvents.length > 0) {
4848
+ this.logger.warn(`${this.name}: Dropping ${orphanedEvents.length} orphaned events on cleanup`, {
4849
+ traceId,
4850
+ orphanedEvents: orphanedEvents.map((e) => ({
4851
+ spanId: e.event.exportedSpan.id,
4852
+ waitingFor: e.waitingFor,
4853
+ attempts: e.attempts,
4854
+ queuedAt: e.queuedAt
4855
+ }))
4856
+ });
4857
+ }
4858
+ this.#traceMap.delete(traceId);
4859
+ this.logger.debug(`${this.name}: Cleaned up trace data`, { traceId });
4860
+ }
4861
+ // ============================================================================
4862
+ // Cap Enforcement
4863
+ // ============================================================================
4864
+ /**
4865
+ * Enforce soft cap on pending cleanup traces.
4866
+ * Only removes traces with activeSpanCount == 0.
4867
+ */
4868
+ #enforcePendingCleanupCap() {
4869
+ if (this.#pendingCleanups.size <= this.#maxPendingCleanupTraces) {
4870
+ return;
4871
+ }
4872
+ const toRemove = this.#pendingCleanups.size - this.#maxPendingCleanupTraces;
4873
+ this.logger.warn(`${this.name}: Pending cleanup cap exceeded, force-cleaning ${toRemove} traces`, {
4874
+ pendingCount: this.#pendingCleanups.size,
4875
+ cap: this.#maxPendingCleanupTraces
4876
+ });
4877
+ let removed = 0;
4878
+ for (const traceId of this.#traceMap.keys()) {
4879
+ if (removed >= toRemove) break;
4880
+ if (this.#pendingCleanups.has(traceId)) {
4881
+ this.#cancelScheduledCleanup(traceId);
4882
+ this.#performCleanup(traceId);
4883
+ removed++;
4884
+ }
4885
+ }
4886
+ }
4887
+ /**
4888
+ * Enforce hard cap on total traces.
4889
+ * Will kill even active traces if necessary.
4890
+ * Uses a flag to prevent concurrent executions when called fire-and-forget.
4891
+ */
4892
+ async #enforceHardCap() {
4893
+ if (this.#traceMap.size <= this.#maxTotalTraces || this.#hardCapEnforcementInProgress) {
4894
+ return;
4895
+ }
4896
+ this.#hardCapEnforcementInProgress = true;
4897
+ try {
4898
+ if (this.#traceMap.size <= this.#maxTotalTraces) {
4899
+ return;
4900
+ }
4901
+ const toRemove = this.#traceMap.size - this.#maxTotalTraces;
4902
+ this.logger.warn(`${this.name}: Total trace cap exceeded, killing ${toRemove} oldest traces`, {
4903
+ traceCount: this.#traceMap.size,
4904
+ cap: this.#maxTotalTraces
4905
+ });
4906
+ const reason = {
4907
+ id: "TRACE_CAP_EXCEEDED",
4908
+ message: "Trace killed due to memory cap enforcement.",
4909
+ domain: "MASTRA_OBSERVABILITY",
4910
+ category: "SYSTEM"
4911
+ };
4912
+ let removed = 0;
4913
+ for (const traceId of [...this.#traceMap.keys()]) {
4914
+ if (removed >= toRemove) break;
4915
+ const traceData = this.#traceMap.get(traceId);
4916
+ if (traceData) {
4917
+ for (const spanId of traceData.activeSpanIds) {
4918
+ const span = traceData.getSpan({ spanId });
4919
+ if (span) {
4920
+ await this._abortSpan({ span, traceData, reason });
4921
+ }
4922
+ }
4923
+ this.#cancelScheduledCleanup(traceId);
4924
+ this.#performCleanup(traceId);
4925
+ removed++;
4926
+ }
4927
+ }
4928
+ } finally {
4929
+ this.#hardCapEnforcementInProgress = false;
4930
+ }
4931
+ }
4932
+ // ============================================================================
4933
+ // Lifecycle Hooks (Override in subclass)
4934
+ // ============================================================================
4935
+ /**
4936
+ * Hook called before processing each tracing event.
4937
+ * Override to transform or enrich the event before processing.
4938
+ *
4939
+ * Note: The customSpanFormatter is applied at the BaseExporter level before this hook.
4940
+ * Subclasses can override this to add additional pre-processing logic.
4941
+ *
4942
+ * @param event - The incoming tracing event
4943
+ * @returns The (possibly modified) event to process
4944
+ */
4945
+ async _preExportTracingEvent(event) {
4946
+ return event;
4947
+ }
4948
+ /**
4949
+ * Hook called after processing each tracing event.
4950
+ * Override to perform post-processing actions like flushing.
4951
+ */
4952
+ async _postExportTracingEvent() {
4953
+ }
4954
+ // ============================================================================
4955
+ // Behavior Flags (Override in subclass as needed)
4956
+ // ============================================================================
4957
+ /**
4958
+ * If true, skip calling _buildRoot and let root spans go through _buildSpan.
4959
+ * Use when the vendor doesn't have a separate trace/root concept.
4960
+ * @default false
4961
+ */
4962
+ skipBuildRootTask = false;
4963
+ /**
4964
+ * If true, skip processing span_updated events entirely.
4965
+ * Use when the vendor doesn't support incremental span updates.
4966
+ * @default false
4967
+ */
4968
+ skipSpanUpdateEvents = false;
4969
+ /**
4970
+ * If true, don't cache event spans in TraceData.
4971
+ * Use when events can't be parents of other spans.
4972
+ * @default false
4973
+ */
4974
+ skipCachingEventSpans = false;
4975
+ getMethod(event) {
4976
+ if (event.exportedSpan.isEvent) {
4977
+ return "handleEventSpan";
4978
+ }
4979
+ const eventType = event.type;
4980
+ switch (eventType) {
4981
+ case TracingEventType.SPAN_STARTED:
4982
+ return "handleSpanStart";
4983
+ case TracingEventType.SPAN_UPDATED:
4984
+ return "handleSpanUpdate";
4985
+ case TracingEventType.SPAN_ENDED:
4986
+ return "handleSpanEnd";
4987
+ default: {
4988
+ const _exhaustiveCheck = eventType;
4989
+ throw new Error(`Unhandled event type: ${_exhaustiveCheck}`);
4990
+ }
4991
+ }
4992
+ }
4993
+ async _exportTracingEvent(event) {
4994
+ if (this.#shutdownStarted) {
4995
+ return;
4996
+ }
4997
+ const method = this.getMethod(event);
4998
+ if (method == "handleSpanUpdate" && this.skipSpanUpdateEvents) {
4999
+ return;
5000
+ }
5001
+ const traceId = event.exportedSpan.traceId;
5002
+ const traceData = this.getTraceData({ traceId, method });
5003
+ const { exportedSpan } = await this._preExportTracingEvent(event);
5004
+ if (!this.skipBuildRootTask && !traceData.hasRoot()) {
5005
+ if (exportedSpan.isRootSpan) {
5006
+ this.logger.debug(`${this.name}: Building root`, {
5007
+ traceId: exportedSpan.traceId,
5008
+ spanId: exportedSpan.id
5009
+ });
5010
+ const rootData = await this._buildRoot({ span: exportedSpan, traceData });
5011
+ if (rootData) {
5012
+ this.logger.debug(`${this.name}: Adding root`, {
5013
+ traceId: exportedSpan.traceId,
5014
+ spanId: exportedSpan.id
5015
+ });
5016
+ traceData.addRoot({ rootId: exportedSpan.id, rootData });
5017
+ this.#scheduleProcessWaitingForRoot(traceId);
5018
+ }
5019
+ } else {
5020
+ this.logger.debug(`${this.name}: Root does not exist, adding span to waiting queue.`, {
5021
+ traceId: exportedSpan.traceId,
5022
+ spanId: exportedSpan.id
5023
+ });
5024
+ traceData.addToWaitingQueue({ event, waitingFor: "root" });
5025
+ return;
5026
+ }
5027
+ }
5028
+ if (exportedSpan.metadata && this.name in exportedSpan.metadata) {
5029
+ const metadata = exportedSpan.metadata[this.name];
5030
+ this.logger.debug(`${this.name}: Found provider metadata in span`, {
5031
+ traceId: exportedSpan.traceId,
5032
+ spanId: exportedSpan.id,
5033
+ metadata
5034
+ });
5035
+ traceData.addMetadata({ spanId: exportedSpan.id, metadata });
5036
+ }
5037
+ try {
5038
+ switch (method) {
5039
+ case "handleEventSpan": {
5040
+ this.logger.debug(`${this.name}: handling event`, {
5041
+ traceId: exportedSpan.traceId,
5042
+ spanId: exportedSpan.id
5043
+ });
5044
+ traceData.addBranch({ spanId: exportedSpan.id, parentSpanId: exportedSpan.parentSpanId });
5045
+ const eventData = await this._buildEvent({ span: exportedSpan, traceData });
5046
+ if (eventData) {
5047
+ if (!this.skipCachingEventSpans) {
5048
+ this.logger.debug(`${this.name}: adding event to traceData`, {
5049
+ traceId: exportedSpan.traceId,
5050
+ spanId: exportedSpan.id
5051
+ });
5052
+ traceData.addEvent({ eventId: exportedSpan.id, eventData });
5053
+ }
5054
+ this.#scheduleProcessWaitingFor(traceId, exportedSpan.id);
5055
+ } else {
5056
+ const parentId = exportedSpan.parentSpanId;
5057
+ this.logger.debug(`${this.name}: adding event to waiting queue`, {
5058
+ traceId: exportedSpan.traceId,
5059
+ spanId: exportedSpan.id,
5060
+ waitingFor: parentId ?? "root"
5061
+ });
5062
+ traceData.addToWaitingQueue({ event, waitingFor: parentId ?? "root" });
5063
+ }
5064
+ break;
5065
+ }
5066
+ case "handleSpanStart": {
5067
+ this.logger.debug(`${this.name}: handling span start`, {
5068
+ traceId: exportedSpan.traceId,
5069
+ spanId: exportedSpan.id
5070
+ });
5071
+ traceData.addBranch({ spanId: exportedSpan.id, parentSpanId: exportedSpan.parentSpanId });
5072
+ const spanData = await this._buildSpan({ span: exportedSpan, traceData });
5073
+ if (spanData) {
5074
+ this.logger.debug(`${this.name}: adding span to traceData`, {
5075
+ traceId: exportedSpan.traceId,
5076
+ spanId: exportedSpan.id
5077
+ });
5078
+ traceData.addSpan({ spanId: exportedSpan.id, spanData });
5079
+ if (exportedSpan.isRootSpan) {
5080
+ traceData.markRootSpanProcessed();
5081
+ this.#scheduleProcessWaitingForRoot(traceId);
5082
+ }
5083
+ this.#scheduleProcessWaitingFor(traceId, exportedSpan.id);
5084
+ } else {
5085
+ const parentId = exportedSpan.parentSpanId;
5086
+ this.logger.debug(`${this.name}: adding span to waiting queue`, {
5087
+ traceId: exportedSpan.traceId,
5088
+ waitingFor: parentId ?? "root"
5089
+ });
5090
+ traceData.addToWaitingQueue({ event, waitingFor: parentId ?? "root" });
5091
+ }
5092
+ break;
5093
+ }
5094
+ case "handleSpanUpdate":
5095
+ this.logger.debug(`${this.name}: handling span update`, {
5096
+ traceId: exportedSpan.traceId,
5097
+ spanId: exportedSpan.id
5098
+ });
5099
+ await this._updateSpan({ span: exportedSpan, traceData });
5100
+ break;
5101
+ case "handleSpanEnd":
5102
+ this.logger.debug(`${this.name}: handling span end`, {
5103
+ traceId: exportedSpan.traceId,
5104
+ spanId: exportedSpan.id
5105
+ });
5106
+ traceData.endSpan({ spanId: exportedSpan.id });
5107
+ await this._finishSpan({ span: exportedSpan, traceData });
5108
+ if (traceData.activeSpanCount() === 0) {
5109
+ this.#scheduleCleanup(traceId);
5110
+ }
5111
+ break;
5112
+ }
5113
+ } catch (error) {
5114
+ this.logger.error(`${this.name}: exporter error`, { error, event, method });
5115
+ }
5116
+ if (traceData.activeSpanCount() === 0) {
5117
+ this.#scheduleCleanup(traceId);
5118
+ }
5119
+ await this._postExportTracingEvent();
5120
+ }
5121
+ // ============================================================================
5122
+ // Protected Helpers
5123
+ // ============================================================================
5124
+ /**
5125
+ * Get or create the TraceData container for a trace.
5126
+ * Also cancels any pending cleanup since new data has arrived.
5127
+ *
5128
+ * @param args.traceId - The trace ID
5129
+ * @param args.method - The calling method name (for logging)
5130
+ * @returns The TraceData container for this trace
5131
+ */
5132
+ getTraceData(args) {
5133
+ const { traceId, method } = args;
5134
+ this.#cancelScheduledCleanup(traceId);
5135
+ if (!this.#traceMap.has(traceId)) {
5136
+ this.#traceMap.set(traceId, new TraceData());
5137
+ this.logger.debug(`${this.name}: Created new trace data cache`, {
5138
+ traceId,
5139
+ method
5140
+ });
5141
+ this.#enforceHardCap().catch((error) => {
5142
+ this.logger.error(`${this.name}: Error enforcing hard cap`, { error });
5143
+ });
5144
+ }
5145
+ return this.#traceMap.get(traceId);
5146
+ }
5147
+ /**
5148
+ * Get the current number of traces being tracked.
5149
+ * @returns The trace count
5150
+ */
5151
+ traceMapSize() {
5152
+ return this.#traceMap.size;
5153
+ }
5154
+ // ============================================================================
5155
+ // Flush and Shutdown Hooks (Override in subclass as needed)
5156
+ // ============================================================================
5157
+ /**
5158
+ * Hook called by flush() to perform vendor-specific flush logic.
5159
+ * Override to send buffered data to the vendor's API.
5160
+ *
5161
+ * Unlike _postShutdown(), this method should NOT release resources,
5162
+ * as the exporter will continue to be used after flushing.
5163
+ */
5164
+ async _flush() {
5165
+ }
5166
+ /**
5167
+ * Force flush any buffered data without shutting down the exporter.
5168
+ * This is useful in serverless environments where you need to ensure spans
5169
+ * are exported before the runtime instance is terminated.
5170
+ *
5171
+ * Subclasses should override _flush() to implement vendor-specific flush logic.
5172
+ */
5173
+ async flush() {
5174
+ if (this.isDisabled) {
5175
+ return;
5176
+ }
5177
+ this.logger.debug(`${this.name}: Flushing`);
5178
+ await this._flush();
5179
+ }
5180
+ /**
5181
+ * Hook called at the start of shutdown, before cancelling timers and aborting spans.
5182
+ * Override to perform vendor-specific pre-shutdown tasks.
5183
+ */
5184
+ async _preShutdown() {
5185
+ }
5186
+ /**
5187
+ * Hook called at the end of shutdown, after all spans are aborted.
5188
+ * Override to perform vendor-specific cleanup (e.g., flushing).
5189
+ */
5190
+ async _postShutdown() {
5191
+ }
5192
+ /**
5193
+ * Gracefully shut down the exporter.
5194
+ * Cancels all pending cleanup timers, aborts all active spans, and clears state.
5195
+ */
5196
+ async shutdown() {
5197
+ if (this.isDisabled) {
5198
+ return;
5199
+ }
5200
+ this.#shutdownStarted = true;
5201
+ await this._preShutdown();
5202
+ for (const [traceId, timeout] of this.#pendingCleanups) {
5203
+ clearTimeout(timeout);
5204
+ this.logger.debug(`${this.name}: Cancelled pending cleanup on shutdown`, { traceId });
5205
+ }
5206
+ this.#pendingCleanups.clear();
5207
+ const reason = {
5208
+ id: "SHUTDOWN",
5209
+ message: "Observability is shutting down.",
5210
+ domain: "MASTRA_OBSERVABILITY",
5211
+ category: "SYSTEM"
5212
+ };
5213
+ for (const [traceId, traceData] of this.#traceMap) {
5214
+ const orphanedEvents = traceData.getAllQueuedEvents();
5215
+ if (orphanedEvents.length > 0) {
5216
+ this.logger.warn(`${this.name}: Dropping ${orphanedEvents.length} orphaned events on shutdown`, {
5217
+ traceId,
5218
+ orphanedEvents: orphanedEvents.map((e) => ({
5219
+ spanId: e.event.exportedSpan.id,
5220
+ waitingFor: e.waitingFor,
5221
+ attempts: e.attempts
5222
+ }))
5223
+ });
5224
+ }
5225
+ for (const spanId of traceData.activeSpanIds) {
5226
+ const span = traceData.getSpan({ spanId });
5227
+ if (span) {
5228
+ await this._abortSpan({ span, traceData, reason });
5229
+ }
5230
+ }
5231
+ }
5232
+ this.#traceMap.clear();
5233
+ await this._postShutdown();
5234
+ await super.shutdown();
5235
+ }
5236
+ };
5237
+
5238
+ // src/exporters/span-formatters.ts
5239
+ function chainFormatters(formatters) {
5240
+ return async (span) => {
5241
+ let currentSpan = span;
5242
+ for (const formatter of formatters) {
5243
+ currentSpan = await formatter(currentSpan);
5244
+ }
5245
+ return currentSpan;
5246
+ };
5247
+ }
4228
5248
  var CloudExporter = class extends BaseExporter {
4229
5249
  name = "mastra-cloud-observability-exporter";
4230
- config;
5250
+ cloudConfig;
4231
5251
  buffer;
4232
5252
  flushTimer = null;
4233
5253
  constructor(config = {}) {
4234
5254
  super(config);
4235
5255
  const accessToken = config.accessToken ?? process.env.MASTRA_CLOUD_ACCESS_TOKEN;
4236
5256
  if (!accessToken) {
4237
- this.setDisabled(
4238
- "MASTRA_CLOUD_ACCESS_TOKEN environment variable not set.\n\u{1F680} Sign up at https://cloud.mastra.ai to see your AI traces online and obtain your access token."
4239
- );
5257
+ this.setDisabled("MASTRA_CLOUD_ACCESS_TOKEN environment variable not set.");
4240
5258
  }
4241
5259
  const endpoint = config.endpoint ?? process.env.MASTRA_CLOUD_TRACES_ENDPOINT ?? "https://api.mastra.ai/ai/spans/publish";
4242
- this.config = {
5260
+ this.cloudConfig = {
4243
5261
  logger: this.logger,
4244
5262
  logLevel: config.logLevel ?? LogLevel.INFO,
4245
5263
  maxBatchSize: config.maxBatchSize ?? 1e3,
@@ -4297,12 +5315,12 @@ var CloudExporter = class extends BaseExporter {
4297
5315
  return spanRecord;
4298
5316
  }
4299
5317
  shouldFlush() {
4300
- if (this.buffer.totalSize >= this.config.maxBatchSize) {
5318
+ if (this.buffer.totalSize >= this.cloudConfig.maxBatchSize) {
4301
5319
  return true;
4302
5320
  }
4303
5321
  if (this.buffer.firstEventTime && this.buffer.totalSize > 0) {
4304
5322
  const elapsed = Date.now() - this.buffer.firstEventTime.getTime();
4305
- if (elapsed >= this.config.maxBatchWaitMs) {
5323
+ if (elapsed >= this.cloudConfig.maxBatchWaitMs) {
4306
5324
  return true;
4307
5325
  }
4308
5326
  }
@@ -4325,9 +5343,9 @@ var CloudExporter = class extends BaseExporter {
4325
5343
  this.logger.trackException(mastraError);
4326
5344
  this.logger.error("Scheduled flush failed", mastraError);
4327
5345
  });
4328
- }, this.config.maxBatchWaitMs);
5346
+ }, this.cloudConfig.maxBatchWaitMs);
4329
5347
  }
4330
- async flush() {
5348
+ async flushBuffer() {
4331
5349
  if (this.flushTimer) {
4332
5350
  clearTimeout(this.flushTimer);
4333
5351
  this.flushTimer = null;
@@ -4337,7 +5355,7 @@ var CloudExporter = class extends BaseExporter {
4337
5355
  }
4338
5356
  const startTime = Date.now();
4339
5357
  const spansCopy = [...this.buffer.spans];
4340
- const flushReason = this.buffer.totalSize >= this.config.maxBatchSize ? "size" : "time";
5358
+ const flushReason = this.buffer.totalSize >= this.cloudConfig.maxBatchSize ? "size" : "time";
4341
5359
  this.resetBuffer();
4342
5360
  try {
4343
5361
  await this.batchUpload(spansCopy);
@@ -4368,7 +5386,7 @@ var CloudExporter = class extends BaseExporter {
4368
5386
  */
4369
5387
  async batchUpload(spans) {
4370
5388
  const headers = {
4371
- Authorization: `Bearer ${this.config.accessToken}`,
5389
+ Authorization: `Bearer ${this.cloudConfig.accessToken}`,
4372
5390
  "Content-Type": "application/json"
4373
5391
  };
4374
5392
  const options = {
@@ -4376,13 +5394,29 @@ var CloudExporter = class extends BaseExporter {
4376
5394
  headers,
4377
5395
  body: JSON.stringify({ spans })
4378
5396
  };
4379
- await fetchWithRetry(this.config.endpoint, options, this.config.maxRetries);
5397
+ await fetchWithRetry(this.cloudConfig.endpoint, options, this.cloudConfig.maxRetries);
4380
5398
  }
4381
5399
  resetBuffer() {
4382
5400
  this.buffer.spans = [];
4383
5401
  this.buffer.firstEventTime = void 0;
4384
5402
  this.buffer.totalSize = 0;
4385
5403
  }
5404
+ /**
5405
+ * Force flush any buffered spans without shutting down the exporter.
5406
+ * This is useful in serverless environments where you need to ensure spans
5407
+ * are exported before the runtime instance is terminated.
5408
+ */
5409
+ async flush() {
5410
+ if (this.isDisabled) {
5411
+ return;
5412
+ }
5413
+ if (this.buffer.totalSize > 0) {
5414
+ this.logger.debug("Flushing buffered events", {
5415
+ bufferedEvents: this.buffer.totalSize
5416
+ });
5417
+ await this.flushBuffer();
5418
+ }
5419
+ }
4386
5420
  async shutdown() {
4387
5421
  if (this.isDisabled) {
4388
5422
  return;
@@ -4391,27 +5425,22 @@ var CloudExporter = class extends BaseExporter {
4391
5425
  clearTimeout(this.flushTimer);
4392
5426
  this.flushTimer = null;
4393
5427
  }
4394
- if (this.buffer.totalSize > 0) {
4395
- this.logger.info("Flushing remaining events on shutdown", {
4396
- remainingEvents: this.buffer.totalSize
4397
- });
4398
- try {
4399
- await this.flush();
4400
- } catch (error) {
4401
- const mastraError = new MastraError(
4402
- {
4403
- id: `CLOUD_EXPORTER_FAILED_TO_FLUSH_REMAINING_EVENTS_DURING_SHUTDOWN`,
4404
- domain: ErrorDomain.MASTRA_OBSERVABILITY,
4405
- category: ErrorCategory.USER,
4406
- details: {
4407
- remainingEvents: this.buffer.totalSize
4408
- }
4409
- },
4410
- error
4411
- );
4412
- this.logger.trackException(mastraError);
4413
- this.logger.error("Failed to flush remaining events during shutdown", mastraError);
4414
- }
5428
+ try {
5429
+ await this.flush();
5430
+ } catch (error) {
5431
+ const mastraError = new MastraError(
5432
+ {
5433
+ id: `CLOUD_EXPORTER_FAILED_TO_FLUSH_REMAINING_EVENTS_DURING_SHUTDOWN`,
5434
+ domain: ErrorDomain.MASTRA_OBSERVABILITY,
5435
+ category: ErrorCategory.USER,
5436
+ details: {
5437
+ remainingEvents: this.buffer.totalSize
5438
+ }
5439
+ },
5440
+ error
5441
+ );
5442
+ this.logger.trackException(mastraError);
5443
+ this.logger.error("Failed to flush remaining events during shutdown", mastraError);
4415
5444
  }
4416
5445
  this.logger.info("CloudExporter shutdown complete");
4417
5446
  }
@@ -4715,7 +5744,7 @@ var DefaultExporter = class extends BaseExporter {
4715
5744
  clearTimeout(this.#flushTimer);
4716
5745
  }
4717
5746
  this.#flushTimer = setTimeout(() => {
4718
- this.flush().catch((error) => {
5747
+ this.flushBuffer().catch((error) => {
4719
5748
  this.logger.error("Scheduled flush failed", {
4720
5749
  error: error instanceof Error ? error.message : String(error)
4721
5750
  });
@@ -4849,7 +5878,7 @@ var DefaultExporter = class extends BaseExporter {
4849
5878
  handleBatchWithUpdatesEvent(event) {
4850
5879
  this.addToBuffer(event);
4851
5880
  if (this.shouldFlush()) {
4852
- this.flush().catch((error) => {
5881
+ this.flushBuffer().catch((error) => {
4853
5882
  this.logger.error("Batch flush failed", {
4854
5883
  error: error instanceof Error ? error.message : String(error)
4855
5884
  });
@@ -4865,7 +5894,7 @@ var DefaultExporter = class extends BaseExporter {
4865
5894
  if (event.type === TracingEventType.SPAN_ENDED) {
4866
5895
  this.addToBuffer(event);
4867
5896
  if (this.shouldFlush()) {
4868
- this.flush().catch((error) => {
5897
+ this.flushBuffer().catch((error) => {
4869
5898
  this.logger.error("Batch flush failed", {
4870
5899
  error: error instanceof Error ? error.message : String(error)
4871
5900
  });
@@ -4882,9 +5911,9 @@ var DefaultExporter = class extends BaseExporter {
4882
5911
  return this.#config.retryDelayMs * Math.pow(2, attempt);
4883
5912
  }
4884
5913
  /**
4885
- * Flushes the current buffer to storage with retry logic
5914
+ * Flushes the current buffer to storage with retry logic (internal implementation)
4886
5915
  */
4887
- async flush() {
5916
+ async flushBuffer() {
4888
5917
  if (!this.#observability) {
4889
5918
  this.logger.debug("Cannot flush traces. Observability storage is not initialized");
4890
5919
  return;
@@ -4991,23 +6020,25 @@ var DefaultExporter = class extends BaseExporter {
4991
6020
  break;
4992
6021
  }
4993
6022
  }
6023
+ /**
6024
+ * Force flush any buffered spans without shutting down the exporter.
6025
+ * This is useful in serverless environments where you need to ensure spans
6026
+ * are exported before the runtime instance is terminated.
6027
+ */
6028
+ async flush() {
6029
+ if (this.buffer.totalSize > 0) {
6030
+ this.logger.debug("Flushing buffered events", {
6031
+ bufferedEvents: this.buffer.totalSize
6032
+ });
6033
+ await this.flushBuffer();
6034
+ }
6035
+ }
4994
6036
  async shutdown() {
4995
6037
  if (this.#flushTimer) {
4996
6038
  clearTimeout(this.#flushTimer);
4997
6039
  this.#flushTimer = null;
4998
6040
  }
4999
- if (this.buffer.totalSize > 0) {
5000
- this.logger.info("Flushing remaining events on shutdown", {
5001
- remainingEvents: this.buffer.totalSize
5002
- });
5003
- try {
5004
- await this.flush();
5005
- } catch (error) {
5006
- this.logger.error("Failed to flush remaining events during shutdown", {
5007
- error: error instanceof Error ? error.message : String(error)
5008
- });
5009
- }
5010
- }
6041
+ await this.flush();
5011
6042
  this.logger.info("DefaultExporter shutdown complete");
5012
6043
  }
5013
6044
  };
@@ -5360,6 +6391,29 @@ var ModelSpanTracker = class {
5360
6391
  break;
5361
6392
  }
5362
6393
  }
6394
+ /**
6395
+ * Handle tool-call-approval chunks.
6396
+ * Creates a span for approval requests so they can be seen in traces for debugging.
6397
+ */
6398
+ #handleToolApprovalChunk(chunk) {
6399
+ if (chunk.type !== "tool-call-approval") return;
6400
+ const payload = chunk.payload;
6401
+ if (!this.#currentStepSpan) {
6402
+ this.startStep();
6403
+ }
6404
+ const span = this.#currentStepSpan?.createEventSpan({
6405
+ name: `chunk: 'tool-call-approval'`,
6406
+ type: SpanType.MODEL_CHUNK,
6407
+ attributes: {
6408
+ chunkType: "tool-call-approval",
6409
+ sequenceNumber: this.#chunkSequence
6410
+ },
6411
+ output: payload
6412
+ });
6413
+ if (span) {
6414
+ this.#chunkSequence++;
6415
+ }
6416
+ }
5363
6417
  /**
5364
6418
  * Handle tool-output chunks from sub-agents.
5365
6419
  * Consolidates streaming text/reasoning deltas into a single span per tool call.
@@ -5480,10 +6534,40 @@ var ModelSpanTracker = class {
5480
6534
  case "step-finish":
5481
6535
  this.#endStepSpan(chunk.payload);
5482
6536
  break;
6537
+ // Infrastructure chunks - skip creating spans for these
6538
+ // They are either redundant, metadata-only, or error/control flow
5483
6539
  case "raw":
5484
- // Skip raw chunks as they're redundant
6540
+ // Redundant raw data
5485
6541
  case "start":
6542
+ // Stream start marker
5486
6543
  case "finish":
6544
+ // Stream finish marker (step-finish already captures this)
6545
+ case "response-metadata":
6546
+ // Response metadata (not semantic content)
6547
+ case "source":
6548
+ // Source references (metadata)
6549
+ case "file":
6550
+ // Binary file data (too large/not semantic)
6551
+ case "error":
6552
+ // Error handling
6553
+ case "abort":
6554
+ // Abort signal
6555
+ case "tripwire":
6556
+ // Processor rejection
6557
+ case "watch":
6558
+ // Internal watch event
6559
+ case "tool-error":
6560
+ // Tool error handling
6561
+ case "tool-call-suspended":
6562
+ // Suspension (not content)
6563
+ case "reasoning-signature":
6564
+ // Signature metadata
6565
+ case "redacted-reasoning":
6566
+ // Redacted content metadata
6567
+ case "step-output":
6568
+ break;
6569
+ case "tool-call-approval":
6570
+ this.#handleToolApprovalChunk(chunk);
5487
6571
  break;
5488
6572
  case "tool-output":
5489
6573
  this.#handleToolOutputChunk(chunk);
@@ -5498,20 +6582,6 @@ var ModelSpanTracker = class {
5498
6582
  this.#createEventSpan(chunk.type, cleanPayload);
5499
6583
  break;
5500
6584
  }
5501
- // Default: auto-create event span for all other chunk types
5502
- default: {
5503
- let outputPayload = chunk.payload;
5504
- if (outputPayload && typeof outputPayload === "object" && "data" in outputPayload) {
5505
- const typedPayload = outputPayload;
5506
- outputPayload = { ...typedPayload };
5507
- if (typedPayload.data) {
5508
- outputPayload.size = typeof typedPayload.data === "string" ? typedPayload.data.length : typedPayload.data instanceof Uint8Array ? typedPayload.data.length : void 0;
5509
- delete outputPayload.data;
5510
- }
5511
- }
5512
- this.#createEventSpan(chunk.type, outputPayload);
5513
- break;
5514
- }
5515
6585
  }
5516
6586
  }
5517
6587
  })
@@ -5712,15 +6782,15 @@ var BaseSpan = class {
5712
6782
  this.attributes = deepClean(options.attributes, this.deepCleanOptions) || {};
5713
6783
  this.metadata = deepClean(options.metadata, this.deepCleanOptions);
5714
6784
  this.parent = options.parent;
5715
- this.startTime = /* @__PURE__ */ new Date();
6785
+ this.startTime = options.startTime ?? /* @__PURE__ */ new Date();
5716
6786
  this.observabilityInstance = observabilityInstance;
5717
6787
  this.isEvent = options.isEvent ?? false;
5718
6788
  this.isInternal = isSpanInternal(this.type, options.tracingPolicy?.internal);
5719
6789
  this.traceState = options.traceState;
5720
6790
  this.tags = !options.parent && options.tags?.length ? options.tags : void 0;
5721
- this.entityType = options.entityType;
5722
- this.entityId = options.entityId;
5723
- this.entityName = options.entityName;
6791
+ this.entityType = options.entityType ?? options.parent?.entityType;
6792
+ this.entityId = options.entityId ?? options.parent?.entityId;
6793
+ this.entityName = options.entityName ?? options.parent?.entityName;
5724
6794
  if (this.isEvent) {
5725
6795
  this.output = deepClean(options.output, this.deepCleanOptions);
5726
6796
  } else {
@@ -5769,6 +6839,8 @@ var BaseSpan = class {
5769
6839
  }
5770
6840
  /** Returns a lightweight span ready for export */
5771
6841
  exportSpan(includeInternalSpans) {
6842
+ const hideInput = this.traceState?.hideInput ?? false;
6843
+ const hideOutput = this.traceState?.hideOutput ?? false;
5772
6844
  return {
5773
6845
  id: this.id,
5774
6846
  traceId: this.traceId,
@@ -5781,8 +6853,8 @@ var BaseSpan = class {
5781
6853
  metadata: this.metadata,
5782
6854
  startTime: this.startTime,
5783
6855
  endTime: this.endTime,
5784
- input: this.input,
5785
- output: this.output,
6856
+ input: hideInput ? void 0 : this.input,
6857
+ output: hideOutput ? void 0 : this.output,
5786
6858
  errorInfo: this.errorInfo,
5787
6859
  isEvent: this.isEvent,
5788
6860
  isRootSpan: this.isRootSpan,
@@ -5822,6 +6894,14 @@ var DefaultSpan = class extends BaseSpan {
5822
6894
  traceId;
5823
6895
  constructor(options, observabilityInstance) {
5824
6896
  super(options, observabilityInstance);
6897
+ if (options.spanId && options.traceId) {
6898
+ this.id = options.spanId;
6899
+ this.traceId = options.traceId;
6900
+ if (options.parentSpanId) {
6901
+ this.parentSpanId = options.parentSpanId;
6902
+ }
6903
+ return;
6904
+ }
5825
6905
  const bridge = observabilityInstance.getBridge();
5826
6906
  if (bridge && !this.isInternal) {
5827
6907
  const bridgeIds = bridge.createSpan(options);
@@ -6035,11 +7115,26 @@ var BaseObservabilityInstance = class extends MastraBase {
6035
7115
  // ============================================================================
6036
7116
  /**
6037
7117
  * Start a new span of a specific SpanType
7118
+ *
7119
+ * Sampling Decision:
7120
+ * - For root spans (no parent): Perform sampling check using the configured strategy
7121
+ * - For child spans: Inherit the sampling decision from the parent
7122
+ * - If parent is a NoOpSpan (not sampled), child is also a NoOpSpan
7123
+ * - If parent is a valid span (sampled), child is also sampled
7124
+ *
7125
+ * This ensures trace-level sampling: either all spans in a trace are sampled or none are.
7126
+ * See: https://github.com/mastra-ai/mastra/issues/11504
6038
7127
  */
6039
7128
  startSpan(options) {
6040
7129
  const { customSamplerOptions, requestContext, metadata, tracingOptions, ...rest } = options;
6041
- if (!this.shouldSample(customSamplerOptions)) {
6042
- return new NoOpSpan({ ...rest, metadata }, this);
7130
+ if (options.parent) {
7131
+ if (!options.parent.isValid) {
7132
+ return new NoOpSpan({ ...rest, metadata }, this);
7133
+ }
7134
+ } else {
7135
+ if (!this.shouldSample(customSamplerOptions)) {
7136
+ return new NoOpSpan({ ...rest, metadata }, this);
7137
+ }
6043
7138
  }
6044
7139
  let traceState;
6045
7140
  if (options.parent) {
@@ -6051,8 +7146,12 @@ var BaseObservabilityInstance = class extends MastraBase {
6051
7146
  const mergedMetadata = metadata || tracingMetadata ? { ...metadata, ...tracingMetadata } : void 0;
6052
7147
  const enrichedMetadata = this.extractMetadataFromRequestContext(requestContext, mergedMetadata, traceState);
6053
7148
  const tags = !options.parent ? tracingOptions?.tags : void 0;
7149
+ const traceId = !options.parent ? options.traceId ?? tracingOptions?.traceId : options.traceId;
7150
+ const parentSpanId = !options.parent ? options.parentSpanId ?? tracingOptions?.parentSpanId : options.parentSpanId;
6054
7151
  const span = this.createSpan({
6055
7152
  ...rest,
7153
+ traceId,
7154
+ parentSpanId,
6056
7155
  metadata: enrichedMetadata,
6057
7156
  traceState,
6058
7157
  tags
@@ -6065,6 +7164,37 @@ var BaseObservabilityInstance = class extends MastraBase {
6065
7164
  }
6066
7165
  return span;
6067
7166
  }
7167
+ /**
7168
+ * Rebuild a span from exported data for lifecycle operations.
7169
+ * Used by durable execution engines (e.g., Inngest) to end/update spans
7170
+ * that were created in a previous durable operation.
7171
+ *
7172
+ * The rebuilt span:
7173
+ * - Does NOT emit SPAN_STARTED (assumes original span already did)
7174
+ * - Can have end(), update(), error() called on it
7175
+ * - Will emit SPAN_ENDED or SPAN_UPDATED when those methods are called
7176
+ *
7177
+ * @param cached - The exported span data to rebuild from
7178
+ * @returns A span that can have lifecycle methods called on it
7179
+ */
7180
+ rebuildSpan(cached) {
7181
+ const span = this.createSpan({
7182
+ name: cached.name,
7183
+ type: cached.type,
7184
+ traceId: cached.traceId,
7185
+ spanId: cached.id,
7186
+ parentSpanId: cached.parentSpanId,
7187
+ startTime: cached.startTime instanceof Date ? cached.startTime : new Date(cached.startTime),
7188
+ input: cached.input,
7189
+ attributes: cached.attributes,
7190
+ metadata: cached.metadata,
7191
+ entityType: cached.entityType,
7192
+ entityId: cached.entityId,
7193
+ entityName: cached.entityName
7194
+ });
7195
+ this.wireSpanLifecycle(span);
7196
+ return span;
7197
+ }
6068
7198
  // ============================================================================
6069
7199
  // Configuration Management
6070
7200
  // ============================================================================
@@ -6167,11 +7297,15 @@ var BaseObservabilityInstance = class extends MastraBase {
6167
7297
  const configuredKeys = this.config.requestContextKeys ?? [];
6168
7298
  const additionalKeys = tracingOptions?.requestContextKeys ?? [];
6169
7299
  const allKeys = [...configuredKeys, ...additionalKeys];
6170
- if (allKeys.length === 0) {
7300
+ const hideInput = tracingOptions?.hideInput;
7301
+ const hideOutput = tracingOptions?.hideOutput;
7302
+ if (allKeys.length === 0 && !hideInput && !hideOutput) {
6171
7303
  return void 0;
6172
7304
  }
6173
7305
  return {
6174
- requestContextKeys: allKeys
7306
+ requestContextKeys: allKeys,
7307
+ ...hideInput !== void 0 && { hideInput },
7308
+ ...hideOutput !== void 0 && { hideOutput }
6175
7309
  };
6176
7310
  }
6177
7311
  /**
@@ -6302,6 +7436,29 @@ var BaseObservabilityInstance = class extends MastraBase {
6302
7436
  this.logger.debug(`[Observability] Initialization started [name=${this.name}]`);
6303
7437
  this.logger.info(`[Observability] Initialized successfully [name=${this.name}]`);
6304
7438
  }
7439
+ /**
7440
+ * Force flush any buffered/queued spans from all exporters and the bridge
7441
+ * without shutting down the observability instance.
7442
+ *
7443
+ * This is useful in serverless environments (like Vercel's fluid compute) where
7444
+ * you need to ensure all spans are exported before the runtime instance is
7445
+ * terminated, while keeping the observability system active for future requests.
7446
+ */
7447
+ async flush() {
7448
+ this.logger.debug(`[Observability] Flush started [name=${this.name}]`);
7449
+ const flushPromises = [...this.exporters.map((e) => e.flush())];
7450
+ if (this.config.bridge) {
7451
+ flushPromises.push(this.config.bridge.flush());
7452
+ }
7453
+ const results = await Promise.allSettled(flushPromises);
7454
+ results.forEach((result, index) => {
7455
+ if (result.status === "rejected") {
7456
+ const targetName = index < this.exporters.length ? this.exporters[index]?.name : "bridge";
7457
+ this.logger.error(`[Observability] Flush error [target=${targetName}]`, result.reason);
7458
+ }
7459
+ });
7460
+ this.logger.debug(`[Observability] Flush completed [name=${this.name}]`);
7461
+ }
6305
7462
  /**
6306
7463
  * Shutdown Observability and clean up resources
6307
7464
  */
@@ -6611,6 +7768,9 @@ var Observability = class extends MastraBase {
6611
7768
  }
6612
7769
  }
6613
7770
  if (config.default?.enabled) {
7771
+ console.warn(
7772
+ '[Mastra Observability] The "default: { enabled: true }" configuration is deprecated and will be removed in a future version. Please use explicit configs with DefaultExporter, CloudExporter, and SensitiveDataFilter instead. See https://mastra.ai/docs/observability/tracing/overview for the recommended configuration.'
7773
+ );
6614
7774
  const defaultInstance = new DefaultObservabilityInstance({
6615
7775
  serviceName: "mastra",
6616
7776
  name: "default",
@@ -6698,6 +7858,6 @@ function buildTracingOptions(...updaters) {
6698
7858
  return updaters.reduce((opts, updater) => updater(opts), {});
6699
7859
  }
6700
7860
 
6701
- 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 };
7861
+ 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 };
6702
7862
  //# sourceMappingURL=index.js.map
6703
7863
  //# sourceMappingURL=index.js.map