@mastra/observability 1.0.0-beta.9 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -3839,7 +3839,7 @@ ZodNaN.create = (params) => {
3839
3839
  ...processCreateParams(params)
3840
3840
  });
3841
3841
  };
3842
- var BRAND = Symbol("zod_brand");
3842
+ var BRAND = /* @__PURE__ */ Symbol("zod_brand");
3843
3843
  var ZodBranded = class extends ZodType {
3844
3844
  _parse(input) {
3845
3845
  const { ctx } = this._processInputParams(input);
@@ -4076,6 +4076,12 @@ var samplingStrategySchema = external_exports.discriminatedUnion("type", [
4076
4076
  sampler: external_exports.function().args(external_exports.any().optional()).returns(external_exports.boolean())
4077
4077
  })
4078
4078
  ]);
4079
+ var serializationOptionsSchema = external_exports.object({
4080
+ maxStringLength: external_exports.number().int().positive().optional(),
4081
+ maxDepth: external_exports.number().int().positive().optional(),
4082
+ maxArrayLength: external_exports.number().int().positive().optional(),
4083
+ maxObjectKeys: external_exports.number().int().positive().optional()
4084
+ }).optional();
4079
4085
  var observabilityInstanceConfigSchema = external_exports.object({
4080
4086
  name: external_exports.string().min(1, "Name is required"),
4081
4087
  serviceName: external_exports.string().min(1, "Service name is required"),
@@ -4084,7 +4090,8 @@ var observabilityInstanceConfigSchema = external_exports.object({
4084
4090
  bridge: external_exports.any().optional(),
4085
4091
  spanOutputProcessors: external_exports.array(external_exports.any()).optional(),
4086
4092
  includeInternalSpans: external_exports.boolean().optional(),
4087
- requestContextKeys: external_exports.array(external_exports.string()).optional()
4093
+ requestContextKeys: external_exports.array(external_exports.string()).optional(),
4094
+ serializationOptions: serializationOptionsSchema
4088
4095
  }).refine(
4089
4096
  (data) => {
4090
4097
  const hasExporters = data.exporters && data.exporters.length > 0;
@@ -4102,7 +4109,8 @@ var observabilityConfigValueSchema = external_exports.object({
4102
4109
  bridge: external_exports.any().optional(),
4103
4110
  spanOutputProcessors: external_exports.array(external_exports.any()).optional(),
4104
4111
  includeInternalSpans: external_exports.boolean().optional(),
4105
- requestContextKeys: external_exports.array(external_exports.string()).optional()
4112
+ requestContextKeys: external_exports.array(external_exports.string()).optional(),
4113
+ serializationOptions: serializationOptionsSchema
4106
4114
  }).refine(
4107
4115
  (data) => {
4108
4116
  const hasExporters = data.exporters && data.exporters.length > 0;
@@ -4155,12 +4163,19 @@ var observabilityRegistryConfigSchema = external_exports.object({
4155
4163
  var BaseExporter = class {
4156
4164
  /** Mastra logger instance */
4157
4165
  logger;
4166
+ /** Base configuration (accessible by subclasses) */
4167
+ baseConfig;
4158
4168
  /** Whether this exporter is disabled */
4159
- isDisabled = false;
4169
+ #disabled = false;
4170
+ /** Public getter for disabled state */
4171
+ get isDisabled() {
4172
+ return this.#disabled;
4173
+ }
4160
4174
  /**
4161
4175
  * Initialize the base exporter with logger
4162
4176
  */
4163
4177
  constructor(config = {}) {
4178
+ this.baseConfig = config;
4164
4179
  const logLevel = this.resolveLogLevel(config.logLevel);
4165
4180
  this.logger = config.logger ?? new logger.ConsoleLogger({ level: logLevel, name: this.constructor.name });
4166
4181
  }
@@ -4195,20 +4210,62 @@ var BaseExporter = class {
4195
4210
  * @param reason - Reason why the exporter is disabled
4196
4211
  */
4197
4212
  setDisabled(reason) {
4198
- this.isDisabled = true;
4213
+ this.#disabled = true;
4199
4214
  this.logger.warn(`${this.name} disabled: ${reason}`);
4200
4215
  }
4216
+ /**
4217
+ * Apply the customSpanFormatter if configured.
4218
+ * This is called automatically by exportTracingEvent before _exportTracingEvent.
4219
+ *
4220
+ * Supports both synchronous and asynchronous formatters. If the formatter
4221
+ * returns a Promise, it will be awaited.
4222
+ *
4223
+ * @param event - The incoming tracing event
4224
+ * @returns The (possibly modified) event to process
4225
+ */
4226
+ async applySpanFormatter(event) {
4227
+ if (this.baseConfig.customSpanFormatter) {
4228
+ try {
4229
+ const formattedSpan = await this.baseConfig.customSpanFormatter(event.exportedSpan);
4230
+ return {
4231
+ ...event,
4232
+ exportedSpan: formattedSpan
4233
+ };
4234
+ } catch (error) {
4235
+ this.logger.error(`${this.name}: Error in customSpanFormatter`, {
4236
+ error,
4237
+ spanId: event.exportedSpan.id,
4238
+ traceId: event.exportedSpan.traceId
4239
+ });
4240
+ }
4241
+ }
4242
+ return event;
4243
+ }
4201
4244
  /**
4202
4245
  * Export a tracing event
4203
4246
  *
4204
- * This method checks if the exporter is disabled before calling _exportEvent.
4205
- * Subclasses should implement _exportEvent instead of overriding this method.
4247
+ * This method checks if the exporter is disabled, applies the customSpanFormatter,
4248
+ * then calls _exportTracingEvent.
4249
+ * Subclasses should implement _exportTracingEvent instead of overriding this method.
4206
4250
  */
4207
4251
  async exportTracingEvent(event) {
4208
4252
  if (this.isDisabled) {
4209
4253
  return;
4210
4254
  }
4211
- await this._exportTracingEvent(event);
4255
+ const processedEvent = await this.applySpanFormatter(event);
4256
+ await this._exportTracingEvent(processedEvent);
4257
+ }
4258
+ /**
4259
+ * Force flush any buffered/queued spans without shutting down the exporter.
4260
+ *
4261
+ * This is useful in serverless environments where you need to ensure spans
4262
+ * are exported before the runtime instance is terminated, while keeping
4263
+ * the exporter active for future requests.
4264
+ *
4265
+ * Default implementation is a no-op. Override to add flush logic.
4266
+ */
4267
+ async flush() {
4268
+ this.logger.debug(`${this.name} flush called (no-op in base class)`);
4212
4269
  }
4213
4270
  /**
4214
4271
  * Shutdown the exporter and clean up resources
@@ -4219,21 +4276,990 @@ var BaseExporter = class {
4219
4276
  this.logger.info(`${this.name} shutdown complete`);
4220
4277
  }
4221
4278
  };
4279
+ var TraceData = class {
4280
+ /** The vendor-specific root/trace object */
4281
+ #rootSpan;
4282
+ /** The span ID of the root span */
4283
+ #rootSpanId;
4284
+ /** Whether a span with isRootSpan=true has been successfully processed */
4285
+ #rootSpanProcessed;
4286
+ /** Maps eventId to vendor-specific event objects */
4287
+ #events;
4288
+ /** Maps spanId to vendor-specific span objects */
4289
+ #spans;
4290
+ /** Maps spanId to parentSpanId, representing the span hierarchy */
4291
+ #tree;
4292
+ /** Set of span IDs that have started but not yet ended */
4293
+ #activeSpanIds;
4294
+ /** Maps spanId to vendor-specific metadata */
4295
+ #metadata;
4296
+ /** Arbitrary key-value storage for per-trace data */
4297
+ #extraData;
4298
+ /** Events waiting for the root span to be processed */
4299
+ #waitingForRoot;
4300
+ /** Events waiting for specific parent spans, keyed by parentSpanId */
4301
+ #waitingForParent;
4302
+ /** When this trace data was created, used for cap enforcement */
4303
+ createdAt;
4304
+ constructor() {
4305
+ this.#events = /* @__PURE__ */ new Map();
4306
+ this.#spans = /* @__PURE__ */ new Map();
4307
+ this.#activeSpanIds = /* @__PURE__ */ new Set();
4308
+ this.#tree = /* @__PURE__ */ new Map();
4309
+ this.#metadata = /* @__PURE__ */ new Map();
4310
+ this.#extraData = /* @__PURE__ */ new Map();
4311
+ this.#rootSpanProcessed = false;
4312
+ this.#waitingForRoot = [];
4313
+ this.#waitingForParent = /* @__PURE__ */ new Map();
4314
+ this.createdAt = /* @__PURE__ */ new Date();
4315
+ }
4316
+ /**
4317
+ * Check if this trace has a root span registered.
4318
+ * @returns True if addRoot() has been called
4319
+ */
4320
+ hasRoot() {
4321
+ return !!this.#rootSpanId;
4322
+ }
4323
+ /**
4324
+ * Register the root span for this trace.
4325
+ * @param args.rootId - The span ID of the root span
4326
+ * @param args.rootData - The vendor-specific root object
4327
+ */
4328
+ addRoot(args) {
4329
+ this.#rootSpanId = args.rootId;
4330
+ this.#rootSpan = args.rootData;
4331
+ this.#rootSpanProcessed = true;
4332
+ }
4333
+ /**
4334
+ * Get the vendor-specific root object.
4335
+ * @returns The root object, or undefined if not yet set
4336
+ */
4337
+ getRoot() {
4338
+ return this.#rootSpan;
4339
+ }
4340
+ /**
4341
+ * Check if a span with isRootSpan=true has been successfully processed.
4342
+ * Set via addRoot() or markRootSpanProcessed().
4343
+ * @returns True if the root span has been processed
4344
+ */
4345
+ isRootProcessed() {
4346
+ return this.#rootSpanProcessed;
4347
+ }
4348
+ /**
4349
+ * Mark that the root span has been processed.
4350
+ * Used by exporters with skipBuildRootTask=true where root goes through _buildSpan
4351
+ * instead of _buildRoot.
4352
+ */
4353
+ markRootSpanProcessed() {
4354
+ this.#rootSpanProcessed = true;
4355
+ }
4356
+ /**
4357
+ * Store an arbitrary value in per-trace storage.
4358
+ * @param key - Storage key
4359
+ * @param value - Value to store
4360
+ */
4361
+ setExtraValue(key, value) {
4362
+ this.#extraData.set(key, value);
4363
+ }
4364
+ /**
4365
+ * Check if a key exists in per-trace storage.
4366
+ * @param key - Storage key
4367
+ * @returns True if the key exists
4368
+ */
4369
+ hasExtraValue(key) {
4370
+ return this.#extraData.has(key);
4371
+ }
4372
+ /**
4373
+ * Get a value from per-trace storage.
4374
+ * @param key - Storage key
4375
+ * @returns The stored value, or undefined if not found
4376
+ */
4377
+ getExtraValue(key) {
4378
+ return this.#extraData.get(key);
4379
+ }
4380
+ // ============================================================================
4381
+ // Early Queue Methods
4382
+ // ============================================================================
4383
+ /**
4384
+ * Add an event to the waiting queue.
4385
+ * @param args.event - The tracing event to queue
4386
+ * @param args.waitingFor - 'root' or a specific parentSpanId
4387
+ * @param args.attempts - Optional: preserve attempts count when re-queuing
4388
+ * @param args.queuedAt - Optional: preserve original queue time when re-queuing
4389
+ */
4390
+ addToWaitingQueue(args) {
4391
+ const queuedEvent = {
4392
+ event: args.event,
4393
+ waitingFor: args.waitingFor,
4394
+ attempts: args.attempts ?? 0,
4395
+ queuedAt: args.queuedAt ?? /* @__PURE__ */ new Date()
4396
+ };
4397
+ if (args.waitingFor === "root") {
4398
+ this.#waitingForRoot.push(queuedEvent);
4399
+ } else {
4400
+ const queue = this.#waitingForParent.get(args.waitingFor) ?? [];
4401
+ queue.push(queuedEvent);
4402
+ this.#waitingForParent.set(args.waitingFor, queue);
4403
+ }
4404
+ }
4405
+ /**
4406
+ * Get all events waiting for the root span.
4407
+ * Returns a copy of the internal array.
4408
+ */
4409
+ getEventsWaitingForRoot() {
4410
+ return [...this.#waitingForRoot];
4411
+ }
4412
+ /**
4413
+ * Get all events waiting for a specific parent span.
4414
+ * Returns a copy of the internal array.
4415
+ */
4416
+ getEventsWaitingFor(args) {
4417
+ return [...this.#waitingForParent.get(args.spanId) ?? []];
4418
+ }
4419
+ /**
4420
+ * Clear the waiting-for-root queue.
4421
+ */
4422
+ clearWaitingForRoot() {
4423
+ this.#waitingForRoot = [];
4424
+ }
4425
+ /**
4426
+ * Clear the waiting queue for a specific parent span.
4427
+ */
4428
+ clearWaitingFor(args) {
4429
+ this.#waitingForParent.delete(args.spanId);
4430
+ }
4431
+ /**
4432
+ * Get total count of events in all waiting queues.
4433
+ */
4434
+ waitingQueueSize() {
4435
+ let count = this.#waitingForRoot.length;
4436
+ for (const queue of this.#waitingForParent.values()) {
4437
+ count += queue.length;
4438
+ }
4439
+ return count;
4440
+ }
4441
+ /**
4442
+ * Get all queued events across all waiting queues.
4443
+ * Used for cleanup and logging orphaned events.
4444
+ * @returns Array of all queued events
4445
+ */
4446
+ getAllQueuedEvents() {
4447
+ const all = [...this.#waitingForRoot];
4448
+ for (const queue of this.#waitingForParent.values()) {
4449
+ all.push(...queue);
4450
+ }
4451
+ return all;
4452
+ }
4453
+ // ============================================================================
4454
+ // Span Tree Methods
4455
+ // ============================================================================
4456
+ /**
4457
+ * Record the parent-child relationship for a span.
4458
+ * @param args.spanId - The child span ID
4459
+ * @param args.parentSpanId - The parent span ID, or undefined for root spans
4460
+ */
4461
+ addBranch(args) {
4462
+ this.#tree.set(args.spanId, args.parentSpanId);
4463
+ }
4464
+ /**
4465
+ * Get the parent span ID for a given span.
4466
+ * @param args.spanId - The span ID to look up
4467
+ * @returns The parent span ID, or undefined if root or not found
4468
+ */
4469
+ getParentId(args) {
4470
+ return this.#tree.get(args.spanId);
4471
+ }
4472
+ // ============================================================================
4473
+ // Span Management Methods
4474
+ // ============================================================================
4475
+ /**
4476
+ * Register a span and mark it as active.
4477
+ * @param args.spanId - The span ID
4478
+ * @param args.spanData - The vendor-specific span object
4479
+ */
4480
+ addSpan(args) {
4481
+ this.#spans.set(args.spanId, args.spanData);
4482
+ this.#activeSpanIds.add(args.spanId);
4483
+ }
4484
+ /**
4485
+ * Check if a span exists (regardless of active state).
4486
+ * @param args.spanId - The span ID to check
4487
+ * @returns True if the span exists
4488
+ */
4489
+ hasSpan(args) {
4490
+ return this.#spans.has(args.spanId);
4491
+ }
4492
+ /**
4493
+ * Get a span by ID.
4494
+ * @param args.spanId - The span ID to look up
4495
+ * @returns The vendor-specific span object, or undefined if not found
4496
+ */
4497
+ getSpan(args) {
4498
+ return this.#spans.get(args.spanId);
4499
+ }
4500
+ /**
4501
+ * Mark a span as ended (no longer active).
4502
+ * @param args.spanId - The span ID to mark as ended
4503
+ */
4504
+ endSpan(args) {
4505
+ this.#activeSpanIds.delete(args.spanId);
4506
+ }
4507
+ /**
4508
+ * Check if a span is currently active (started but not ended).
4509
+ * @param args.spanId - The span ID to check
4510
+ * @returns True if the span is active
4511
+ */
4512
+ isActiveSpan(args) {
4513
+ return this.#activeSpanIds.has(args.spanId);
4514
+ }
4515
+ /**
4516
+ * Get the count of currently active spans.
4517
+ * @returns Number of active spans
4518
+ */
4519
+ activeSpanCount() {
4520
+ return this.#activeSpanIds.size;
4521
+ }
4522
+ /**
4523
+ * Get all active span IDs.
4524
+ * @returns Array of active span IDs
4525
+ */
4526
+ get activeSpanIds() {
4527
+ return [...this.#activeSpanIds];
4528
+ }
4529
+ // ============================================================================
4530
+ // Event Management Methods
4531
+ // ============================================================================
4532
+ /**
4533
+ * Register an event.
4534
+ * @param args.eventId - The event ID
4535
+ * @param args.eventData - The vendor-specific event object
4536
+ */
4537
+ addEvent(args) {
4538
+ this.#events.set(args.eventId, args.eventData);
4539
+ }
4540
+ // ============================================================================
4541
+ // Metadata Methods
4542
+ // ============================================================================
4543
+ /**
4544
+ * Store vendor-specific metadata for a span.
4545
+ * Note: This overwrites any existing metadata for the span.
4546
+ * @param args.spanId - The span ID
4547
+ * @param args.metadata - The vendor-specific metadata
4548
+ */
4549
+ addMetadata(args) {
4550
+ this.#metadata.set(args.spanId, args.metadata);
4551
+ }
4552
+ /**
4553
+ * Get vendor-specific metadata for a span.
4554
+ * @param args.spanId - The span ID
4555
+ * @returns The metadata, or undefined if not found
4556
+ */
4557
+ getMetadata(args) {
4558
+ return this.#metadata.get(args.spanId);
4559
+ }
4560
+ // ============================================================================
4561
+ // Parent Lookup Methods
4562
+ // ============================================================================
4563
+ /**
4564
+ * Get the parent span or event for a given span.
4565
+ * Looks up in both spans and events maps.
4566
+ * @param args.span - The span to find the parent for
4567
+ * @returns The parent span/event object, or undefined if root or not found
4568
+ */
4569
+ getParent(args) {
4570
+ const parentId = args.span.parentSpanId;
4571
+ if (parentId) {
4572
+ if (this.#spans.has(parentId)) {
4573
+ return this.#spans.get(parentId);
4574
+ }
4575
+ if (this.#events.has(parentId)) {
4576
+ return this.#events.get(parentId);
4577
+ }
4578
+ }
4579
+ return void 0;
4580
+ }
4581
+ /**
4582
+ * Get the parent span/event or fall back to the root object.
4583
+ * Useful for vendors that attach child spans to either parent spans or the trace root.
4584
+ * @param args.span - The span to find the parent for
4585
+ * @returns The parent span/event, the root object, or undefined
4586
+ */
4587
+ getParentOrRoot(args) {
4588
+ return this.getParent(args) ?? this.getRoot();
4589
+ }
4590
+ };
4591
+ var DEFAULT_EARLY_QUEUE_MAX_ATTEMPTS = 5;
4592
+ var DEFAULT_EARLY_QUEUE_TTL_MS = 3e4;
4593
+ var DEFAULT_TRACE_CLEANUP_DELAY_MS = 3e4;
4594
+ var DEFAULT_MAX_PENDING_CLEANUP_TRACES = 100;
4595
+ var DEFAULT_MAX_TOTAL_TRACES = 500;
4596
+ var TrackingExporter = class extends BaseExporter {
4597
+ /** Map of traceId to per-trace data container */
4598
+ #traceMap = /* @__PURE__ */ new Map();
4599
+ /** Flag to prevent processing during shutdown */
4600
+ #shutdownStarted = false;
4601
+ /** Flag to prevent concurrent hard cap enforcement */
4602
+ #hardCapEnforcementInProgress = false;
4603
+ /** Map of traceId to scheduled cleanup timeout */
4604
+ #pendingCleanups = /* @__PURE__ */ new Map();
4605
+ // Note: #traceMap maintains insertion order (JS Map spec), so we use
4606
+ // #traceMap.keys() to iterate traces oldest-first for cap enforcement.
4607
+ /** Subclass configuration with resolved values */
4608
+ config;
4609
+ /** Maximum attempts to process a queued event before dropping */
4610
+ #earlyQueueMaxAttempts;
4611
+ /** TTL in milliseconds for queued events */
4612
+ #earlyQueueTTLMs;
4613
+ /** Delay before cleaning up completed traces */
4614
+ #traceCleanupDelayMs;
4615
+ /** Soft cap on traces awaiting cleanup */
4616
+ #maxPendingCleanupTraces;
4617
+ /** Hard cap on total traces (will abort active spans if exceeded) */
4618
+ #maxTotalTraces;
4619
+ constructor(config) {
4620
+ super(config);
4621
+ this.config = config;
4622
+ this.#earlyQueueMaxAttempts = config.earlyQueueMaxAttempts ?? DEFAULT_EARLY_QUEUE_MAX_ATTEMPTS;
4623
+ this.#earlyQueueTTLMs = config.earlyQueueTTLMs ?? DEFAULT_EARLY_QUEUE_TTL_MS;
4624
+ this.#traceCleanupDelayMs = config.traceCleanupDelayMs ?? DEFAULT_TRACE_CLEANUP_DELAY_MS;
4625
+ this.#maxPendingCleanupTraces = config.maxPendingCleanupTraces ?? DEFAULT_MAX_PENDING_CLEANUP_TRACES;
4626
+ this.#maxTotalTraces = config.maxTotalTraces ?? DEFAULT_MAX_TOTAL_TRACES;
4627
+ }
4628
+ // ============================================================================
4629
+ // Early Queue Processing
4630
+ // ============================================================================
4631
+ /**
4632
+ * Schedule async processing of events waiting for root span.
4633
+ * Called after root span is successfully processed.
4634
+ */
4635
+ #scheduleProcessWaitingForRoot(traceId) {
4636
+ setImmediate(() => {
4637
+ this.#processWaitingForRoot(traceId).catch((error) => {
4638
+ this.logger.error(`${this.name}: Error processing waiting-for-root queue`, { error, traceId });
4639
+ });
4640
+ });
4641
+ }
4642
+ /**
4643
+ * Schedule async processing of events waiting for a specific parent span.
4644
+ * Called after a span/event is successfully created.
4645
+ */
4646
+ #scheduleProcessWaitingFor(traceId, spanId) {
4647
+ setImmediate(() => {
4648
+ this.#processWaitingFor(traceId, spanId).catch((error) => {
4649
+ this.logger.error(`${this.name}: Error processing waiting queue`, { error, traceId, spanId });
4650
+ });
4651
+ });
4652
+ }
4653
+ /**
4654
+ * Process all events waiting for root span.
4655
+ */
4656
+ async #processWaitingForRoot(traceId) {
4657
+ if (this.#shutdownStarted) return;
4658
+ const traceData = this.#traceMap.get(traceId);
4659
+ if (!traceData) return;
4660
+ const queue = traceData.getEventsWaitingForRoot();
4661
+ if (queue.length === 0) return;
4662
+ this.logger.debug(`${this.name}: Processing ${queue.length} events waiting for root`, { traceId });
4663
+ const toKeep = [];
4664
+ const now = Date.now();
4665
+ for (const queuedEvent of queue) {
4666
+ if (now - queuedEvent.queuedAt.getTime() > this.#earlyQueueTTLMs) {
4667
+ this.logger.warn(`${this.name}: Dropping event due to TTL expiry`, {
4668
+ traceId,
4669
+ spanId: queuedEvent.event.exportedSpan.id,
4670
+ waitingFor: queuedEvent.waitingFor,
4671
+ queuedAt: queuedEvent.queuedAt,
4672
+ attempts: queuedEvent.attempts
4673
+ });
4674
+ continue;
4675
+ }
4676
+ if (queuedEvent.attempts >= this.#earlyQueueMaxAttempts) {
4677
+ this.logger.warn(`${this.name}: Dropping event due to max attempts`, {
4678
+ traceId,
4679
+ spanId: queuedEvent.event.exportedSpan.id,
4680
+ waitingFor: queuedEvent.waitingFor,
4681
+ attempts: queuedEvent.attempts
4682
+ });
4683
+ continue;
4684
+ }
4685
+ queuedEvent.attempts++;
4686
+ const processed = await this.#tryProcessQueuedEvent(queuedEvent, traceData);
4687
+ if (!processed) {
4688
+ const parentId = queuedEvent.event.exportedSpan.parentSpanId;
4689
+ if (parentId && traceData.isRootProcessed()) {
4690
+ traceData.addToWaitingQueue({
4691
+ event: queuedEvent.event,
4692
+ waitingFor: parentId,
4693
+ attempts: queuedEvent.attempts,
4694
+ queuedAt: queuedEvent.queuedAt
4695
+ });
4696
+ } else {
4697
+ toKeep.push(queuedEvent);
4698
+ }
4699
+ }
4700
+ }
4701
+ traceData.clearWaitingForRoot();
4702
+ for (const event of toKeep) {
4703
+ traceData.addToWaitingQueue({
4704
+ event: event.event,
4705
+ waitingFor: "root",
4706
+ attempts: event.attempts,
4707
+ queuedAt: event.queuedAt
4708
+ });
4709
+ }
4710
+ }
4711
+ /**
4712
+ * Process events waiting for a specific parent span.
4713
+ */
4714
+ async #processWaitingFor(traceId, spanId) {
4715
+ if (this.#shutdownStarted) return;
4716
+ const traceData = this.#traceMap.get(traceId);
4717
+ if (!traceData) return;
4718
+ const queue = traceData.getEventsWaitingFor({ spanId });
4719
+ if (queue.length === 0) return;
4720
+ this.logger.debug(`${this.name}: Processing ${queue.length} events waiting for span`, { traceId, spanId });
4721
+ const toKeep = [];
4722
+ const now = Date.now();
4723
+ for (const queuedEvent of queue) {
4724
+ if (now - queuedEvent.queuedAt.getTime() > this.#earlyQueueTTLMs) {
4725
+ this.logger.warn(`${this.name}: Dropping event due to TTL expiry`, {
4726
+ traceId,
4727
+ spanId: queuedEvent.event.exportedSpan.id,
4728
+ waitingFor: queuedEvent.waitingFor,
4729
+ queuedAt: queuedEvent.queuedAt,
4730
+ attempts: queuedEvent.attempts
4731
+ });
4732
+ continue;
4733
+ }
4734
+ if (queuedEvent.attempts >= this.#earlyQueueMaxAttempts) {
4735
+ this.logger.warn(`${this.name}: Dropping event due to max attempts`, {
4736
+ traceId,
4737
+ spanId: queuedEvent.event.exportedSpan.id,
4738
+ waitingFor: queuedEvent.waitingFor,
4739
+ attempts: queuedEvent.attempts
4740
+ });
4741
+ continue;
4742
+ }
4743
+ queuedEvent.attempts++;
4744
+ const processed = await this.#tryProcessQueuedEvent(queuedEvent, traceData);
4745
+ if (!processed) {
4746
+ toKeep.push(queuedEvent);
4747
+ }
4748
+ }
4749
+ traceData.clearWaitingFor({ spanId });
4750
+ for (const event of toKeep) {
4751
+ traceData.addToWaitingQueue({
4752
+ event: event.event,
4753
+ waitingFor: spanId,
4754
+ attempts: event.attempts,
4755
+ queuedAt: event.queuedAt
4756
+ });
4757
+ }
4758
+ }
4759
+ /**
4760
+ * Try to process a queued event.
4761
+ * Returns true if successfully processed, false if still waiting for dependencies.
4762
+ */
4763
+ async #tryProcessQueuedEvent(queuedEvent, traceData) {
4764
+ const { event } = queuedEvent;
4765
+ const { exportedSpan } = event;
4766
+ const method = this.getMethod(event);
4767
+ try {
4768
+ switch (method) {
4769
+ case "handleEventSpan": {
4770
+ traceData.addBranch({ spanId: exportedSpan.id, parentSpanId: exportedSpan.parentSpanId });
4771
+ const eventData = await this._buildEvent({ span: exportedSpan, traceData });
4772
+ if (eventData) {
4773
+ if (!this.skipCachingEventSpans) {
4774
+ traceData.addEvent({ eventId: exportedSpan.id, eventData });
4775
+ }
4776
+ this.#scheduleProcessWaitingFor(exportedSpan.traceId, exportedSpan.id);
4777
+ return true;
4778
+ }
4779
+ return false;
4780
+ }
4781
+ case "handleSpanStart": {
4782
+ traceData.addBranch({ spanId: exportedSpan.id, parentSpanId: exportedSpan.parentSpanId });
4783
+ const spanData = await this._buildSpan({ span: exportedSpan, traceData });
4784
+ if (spanData) {
4785
+ traceData.addSpan({ spanId: exportedSpan.id, spanData });
4786
+ if (exportedSpan.isRootSpan) {
4787
+ traceData.markRootSpanProcessed();
4788
+ }
4789
+ this.#scheduleProcessWaitingFor(exportedSpan.traceId, exportedSpan.id);
4790
+ return true;
4791
+ }
4792
+ return false;
4793
+ }
4794
+ case "handleSpanUpdate": {
4795
+ await this._updateSpan({ span: exportedSpan, traceData });
4796
+ return true;
4797
+ }
4798
+ case "handleSpanEnd": {
4799
+ traceData.endSpan({ spanId: exportedSpan.id });
4800
+ await this._finishSpan({ span: exportedSpan, traceData });
4801
+ if (traceData.activeSpanCount() === 0) {
4802
+ this.#scheduleCleanup(exportedSpan.traceId);
4803
+ }
4804
+ return true;
4805
+ }
4806
+ default:
4807
+ return false;
4808
+ }
4809
+ } catch (error) {
4810
+ this.logger.error(`${this.name}: Error processing queued event`, { error, event, method });
4811
+ return false;
4812
+ }
4813
+ }
4814
+ // ============================================================================
4815
+ // Delayed Cleanup
4816
+ // ============================================================================
4817
+ /**
4818
+ * Schedule cleanup of trace data after a delay.
4819
+ * Allows late-arriving data to still be processed.
4820
+ */
4821
+ #scheduleCleanup(traceId) {
4822
+ this.#cancelScheduledCleanup(traceId);
4823
+ this.logger.debug(`${this.name}: Scheduling cleanup in ${this.#traceCleanupDelayMs}ms`, { traceId });
4824
+ const timeout = setTimeout(() => {
4825
+ this.#pendingCleanups.delete(traceId);
4826
+ this.#performCleanup(traceId);
4827
+ }, this.#traceCleanupDelayMs);
4828
+ this.#pendingCleanups.set(traceId, timeout);
4829
+ this.#enforcePendingCleanupCap();
4830
+ }
4831
+ /**
4832
+ * Cancel a scheduled cleanup for a trace.
4833
+ */
4834
+ #cancelScheduledCleanup(traceId) {
4835
+ const existingTimeout = this.#pendingCleanups.get(traceId);
4836
+ if (existingTimeout) {
4837
+ clearTimeout(existingTimeout);
4838
+ this.#pendingCleanups.delete(traceId);
4839
+ this.logger.debug(`${this.name}: Cancelled scheduled cleanup`, { traceId });
4840
+ }
4841
+ }
4842
+ /**
4843
+ * Perform the actual cleanup of trace data.
4844
+ */
4845
+ #performCleanup(traceId) {
4846
+ const traceData = this.#traceMap.get(traceId);
4847
+ if (!traceData) return;
4848
+ const orphanedEvents = traceData.getAllQueuedEvents();
4849
+ if (orphanedEvents.length > 0) {
4850
+ this.logger.warn(`${this.name}: Dropping ${orphanedEvents.length} orphaned events on cleanup`, {
4851
+ traceId,
4852
+ orphanedEvents: orphanedEvents.map((e) => ({
4853
+ spanId: e.event.exportedSpan.id,
4854
+ waitingFor: e.waitingFor,
4855
+ attempts: e.attempts,
4856
+ queuedAt: e.queuedAt
4857
+ }))
4858
+ });
4859
+ }
4860
+ this.#traceMap.delete(traceId);
4861
+ this.logger.debug(`${this.name}: Cleaned up trace data`, { traceId });
4862
+ }
4863
+ // ============================================================================
4864
+ // Cap Enforcement
4865
+ // ============================================================================
4866
+ /**
4867
+ * Enforce soft cap on pending cleanup traces.
4868
+ * Only removes traces with activeSpanCount == 0.
4869
+ */
4870
+ #enforcePendingCleanupCap() {
4871
+ if (this.#pendingCleanups.size <= this.#maxPendingCleanupTraces) {
4872
+ return;
4873
+ }
4874
+ const toRemove = this.#pendingCleanups.size - this.#maxPendingCleanupTraces;
4875
+ this.logger.warn(`${this.name}: Pending cleanup cap exceeded, force-cleaning ${toRemove} traces`, {
4876
+ pendingCount: this.#pendingCleanups.size,
4877
+ cap: this.#maxPendingCleanupTraces
4878
+ });
4879
+ let removed = 0;
4880
+ for (const traceId of this.#traceMap.keys()) {
4881
+ if (removed >= toRemove) break;
4882
+ if (this.#pendingCleanups.has(traceId)) {
4883
+ this.#cancelScheduledCleanup(traceId);
4884
+ this.#performCleanup(traceId);
4885
+ removed++;
4886
+ }
4887
+ }
4888
+ }
4889
+ /**
4890
+ * Enforce hard cap on total traces.
4891
+ * Will kill even active traces if necessary.
4892
+ * Uses a flag to prevent concurrent executions when called fire-and-forget.
4893
+ */
4894
+ async #enforceHardCap() {
4895
+ if (this.#traceMap.size <= this.#maxTotalTraces || this.#hardCapEnforcementInProgress) {
4896
+ return;
4897
+ }
4898
+ this.#hardCapEnforcementInProgress = true;
4899
+ try {
4900
+ if (this.#traceMap.size <= this.#maxTotalTraces) {
4901
+ return;
4902
+ }
4903
+ const toRemove = this.#traceMap.size - this.#maxTotalTraces;
4904
+ this.logger.warn(`${this.name}: Total trace cap exceeded, killing ${toRemove} oldest traces`, {
4905
+ traceCount: this.#traceMap.size,
4906
+ cap: this.#maxTotalTraces
4907
+ });
4908
+ const reason = {
4909
+ id: "TRACE_CAP_EXCEEDED",
4910
+ message: "Trace killed due to memory cap enforcement.",
4911
+ domain: "MASTRA_OBSERVABILITY",
4912
+ category: "SYSTEM"
4913
+ };
4914
+ let removed = 0;
4915
+ for (const traceId of [...this.#traceMap.keys()]) {
4916
+ if (removed >= toRemove) break;
4917
+ const traceData = this.#traceMap.get(traceId);
4918
+ if (traceData) {
4919
+ for (const spanId of traceData.activeSpanIds) {
4920
+ const span = traceData.getSpan({ spanId });
4921
+ if (span) {
4922
+ await this._abortSpan({ span, traceData, reason });
4923
+ }
4924
+ }
4925
+ this.#cancelScheduledCleanup(traceId);
4926
+ this.#performCleanup(traceId);
4927
+ removed++;
4928
+ }
4929
+ }
4930
+ } finally {
4931
+ this.#hardCapEnforcementInProgress = false;
4932
+ }
4933
+ }
4934
+ // ============================================================================
4935
+ // Lifecycle Hooks (Override in subclass)
4936
+ // ============================================================================
4937
+ /**
4938
+ * Hook called before processing each tracing event.
4939
+ * Override to transform or enrich the event before processing.
4940
+ *
4941
+ * Note: The customSpanFormatter is applied at the BaseExporter level before this hook.
4942
+ * Subclasses can override this to add additional pre-processing logic.
4943
+ *
4944
+ * @param event - The incoming tracing event
4945
+ * @returns The (possibly modified) event to process
4946
+ */
4947
+ async _preExportTracingEvent(event) {
4948
+ return event;
4949
+ }
4950
+ /**
4951
+ * Hook called after processing each tracing event.
4952
+ * Override to perform post-processing actions like flushing.
4953
+ */
4954
+ async _postExportTracingEvent() {
4955
+ }
4956
+ // ============================================================================
4957
+ // Behavior Flags (Override in subclass as needed)
4958
+ // ============================================================================
4959
+ /**
4960
+ * If true, skip calling _buildRoot and let root spans go through _buildSpan.
4961
+ * Use when the vendor doesn't have a separate trace/root concept.
4962
+ * @default false
4963
+ */
4964
+ skipBuildRootTask = false;
4965
+ /**
4966
+ * If true, skip processing span_updated events entirely.
4967
+ * Use when the vendor doesn't support incremental span updates.
4968
+ * @default false
4969
+ */
4970
+ skipSpanUpdateEvents = false;
4971
+ /**
4972
+ * If true, don't cache event spans in TraceData.
4973
+ * Use when events can't be parents of other spans.
4974
+ * @default false
4975
+ */
4976
+ skipCachingEventSpans = false;
4977
+ getMethod(event) {
4978
+ if (event.exportedSpan.isEvent) {
4979
+ return "handleEventSpan";
4980
+ }
4981
+ const eventType = event.type;
4982
+ switch (eventType) {
4983
+ case observability.TracingEventType.SPAN_STARTED:
4984
+ return "handleSpanStart";
4985
+ case observability.TracingEventType.SPAN_UPDATED:
4986
+ return "handleSpanUpdate";
4987
+ case observability.TracingEventType.SPAN_ENDED:
4988
+ return "handleSpanEnd";
4989
+ default: {
4990
+ const _exhaustiveCheck = eventType;
4991
+ throw new Error(`Unhandled event type: ${_exhaustiveCheck}`);
4992
+ }
4993
+ }
4994
+ }
4995
+ async _exportTracingEvent(event) {
4996
+ if (this.#shutdownStarted) {
4997
+ return;
4998
+ }
4999
+ const method = this.getMethod(event);
5000
+ if (method == "handleSpanUpdate" && this.skipSpanUpdateEvents) {
5001
+ return;
5002
+ }
5003
+ const traceId = event.exportedSpan.traceId;
5004
+ const traceData = this.getTraceData({ traceId, method });
5005
+ const { exportedSpan } = await this._preExportTracingEvent(event);
5006
+ if (!this.skipBuildRootTask && !traceData.hasRoot()) {
5007
+ if (exportedSpan.isRootSpan) {
5008
+ this.logger.debug(`${this.name}: Building root`, {
5009
+ traceId: exportedSpan.traceId,
5010
+ spanId: exportedSpan.id
5011
+ });
5012
+ const rootData = await this._buildRoot({ span: exportedSpan, traceData });
5013
+ if (rootData) {
5014
+ this.logger.debug(`${this.name}: Adding root`, {
5015
+ traceId: exportedSpan.traceId,
5016
+ spanId: exportedSpan.id
5017
+ });
5018
+ traceData.addRoot({ rootId: exportedSpan.id, rootData });
5019
+ this.#scheduleProcessWaitingForRoot(traceId);
5020
+ }
5021
+ } else {
5022
+ this.logger.debug(`${this.name}: Root does not exist, adding span to waiting queue.`, {
5023
+ traceId: exportedSpan.traceId,
5024
+ spanId: exportedSpan.id
5025
+ });
5026
+ traceData.addToWaitingQueue({ event, waitingFor: "root" });
5027
+ return;
5028
+ }
5029
+ }
5030
+ if (exportedSpan.metadata && this.name in exportedSpan.metadata) {
5031
+ const metadata = exportedSpan.metadata[this.name];
5032
+ this.logger.debug(`${this.name}: Found provider metadata in span`, {
5033
+ traceId: exportedSpan.traceId,
5034
+ spanId: exportedSpan.id,
5035
+ metadata
5036
+ });
5037
+ traceData.addMetadata({ spanId: exportedSpan.id, metadata });
5038
+ }
5039
+ try {
5040
+ switch (method) {
5041
+ case "handleEventSpan": {
5042
+ this.logger.debug(`${this.name}: handling event`, {
5043
+ traceId: exportedSpan.traceId,
5044
+ spanId: exportedSpan.id
5045
+ });
5046
+ traceData.addBranch({ spanId: exportedSpan.id, parentSpanId: exportedSpan.parentSpanId });
5047
+ const eventData = await this._buildEvent({ span: exportedSpan, traceData });
5048
+ if (eventData) {
5049
+ if (!this.skipCachingEventSpans) {
5050
+ this.logger.debug(`${this.name}: adding event to traceData`, {
5051
+ traceId: exportedSpan.traceId,
5052
+ spanId: exportedSpan.id
5053
+ });
5054
+ traceData.addEvent({ eventId: exportedSpan.id, eventData });
5055
+ }
5056
+ this.#scheduleProcessWaitingFor(traceId, exportedSpan.id);
5057
+ } else {
5058
+ const parentId = exportedSpan.parentSpanId;
5059
+ this.logger.debug(`${this.name}: adding event to waiting queue`, {
5060
+ traceId: exportedSpan.traceId,
5061
+ spanId: exportedSpan.id,
5062
+ waitingFor: parentId ?? "root"
5063
+ });
5064
+ traceData.addToWaitingQueue({ event, waitingFor: parentId ?? "root" });
5065
+ }
5066
+ break;
5067
+ }
5068
+ case "handleSpanStart": {
5069
+ this.logger.debug(`${this.name}: handling span start`, {
5070
+ traceId: exportedSpan.traceId,
5071
+ spanId: exportedSpan.id
5072
+ });
5073
+ traceData.addBranch({ spanId: exportedSpan.id, parentSpanId: exportedSpan.parentSpanId });
5074
+ const spanData = await this._buildSpan({ span: exportedSpan, traceData });
5075
+ if (spanData) {
5076
+ this.logger.debug(`${this.name}: adding span to traceData`, {
5077
+ traceId: exportedSpan.traceId,
5078
+ spanId: exportedSpan.id
5079
+ });
5080
+ traceData.addSpan({ spanId: exportedSpan.id, spanData });
5081
+ if (exportedSpan.isRootSpan) {
5082
+ traceData.markRootSpanProcessed();
5083
+ this.#scheduleProcessWaitingForRoot(traceId);
5084
+ }
5085
+ this.#scheduleProcessWaitingFor(traceId, exportedSpan.id);
5086
+ } else {
5087
+ const parentId = exportedSpan.parentSpanId;
5088
+ this.logger.debug(`${this.name}: adding span to waiting queue`, {
5089
+ traceId: exportedSpan.traceId,
5090
+ waitingFor: parentId ?? "root"
5091
+ });
5092
+ traceData.addToWaitingQueue({ event, waitingFor: parentId ?? "root" });
5093
+ }
5094
+ break;
5095
+ }
5096
+ case "handleSpanUpdate":
5097
+ this.logger.debug(`${this.name}: handling span update`, {
5098
+ traceId: exportedSpan.traceId,
5099
+ spanId: exportedSpan.id
5100
+ });
5101
+ await this._updateSpan({ span: exportedSpan, traceData });
5102
+ break;
5103
+ case "handleSpanEnd":
5104
+ this.logger.debug(`${this.name}: handling span end`, {
5105
+ traceId: exportedSpan.traceId,
5106
+ spanId: exportedSpan.id
5107
+ });
5108
+ traceData.endSpan({ spanId: exportedSpan.id });
5109
+ await this._finishSpan({ span: exportedSpan, traceData });
5110
+ if (traceData.activeSpanCount() === 0) {
5111
+ this.#scheduleCleanup(traceId);
5112
+ }
5113
+ break;
5114
+ }
5115
+ } catch (error) {
5116
+ this.logger.error(`${this.name}: exporter error`, { error, event, method });
5117
+ }
5118
+ if (traceData.activeSpanCount() === 0) {
5119
+ this.#scheduleCleanup(traceId);
5120
+ }
5121
+ await this._postExportTracingEvent();
5122
+ }
5123
+ // ============================================================================
5124
+ // Protected Helpers
5125
+ // ============================================================================
5126
+ /**
5127
+ * Get or create the TraceData container for a trace.
5128
+ * Also cancels any pending cleanup since new data has arrived.
5129
+ *
5130
+ * @param args.traceId - The trace ID
5131
+ * @param args.method - The calling method name (for logging)
5132
+ * @returns The TraceData container for this trace
5133
+ */
5134
+ getTraceData(args) {
5135
+ const { traceId, method } = args;
5136
+ this.#cancelScheduledCleanup(traceId);
5137
+ if (!this.#traceMap.has(traceId)) {
5138
+ this.#traceMap.set(traceId, new TraceData());
5139
+ this.logger.debug(`${this.name}: Created new trace data cache`, {
5140
+ traceId,
5141
+ method
5142
+ });
5143
+ this.#enforceHardCap().catch((error) => {
5144
+ this.logger.error(`${this.name}: Error enforcing hard cap`, { error });
5145
+ });
5146
+ }
5147
+ return this.#traceMap.get(traceId);
5148
+ }
5149
+ /**
5150
+ * Get the current number of traces being tracked.
5151
+ * @returns The trace count
5152
+ */
5153
+ traceMapSize() {
5154
+ return this.#traceMap.size;
5155
+ }
5156
+ // ============================================================================
5157
+ // Flush and Shutdown Hooks (Override in subclass as needed)
5158
+ // ============================================================================
5159
+ /**
5160
+ * Hook called by flush() to perform vendor-specific flush logic.
5161
+ * Override to send buffered data to the vendor's API.
5162
+ *
5163
+ * Unlike _postShutdown(), this method should NOT release resources,
5164
+ * as the exporter will continue to be used after flushing.
5165
+ */
5166
+ async _flush() {
5167
+ }
5168
+ /**
5169
+ * Force flush any buffered data without shutting down the exporter.
5170
+ * This is useful in serverless environments where you need to ensure spans
5171
+ * are exported before the runtime instance is terminated.
5172
+ *
5173
+ * Subclasses should override _flush() to implement vendor-specific flush logic.
5174
+ */
5175
+ async flush() {
5176
+ if (this.isDisabled) {
5177
+ return;
5178
+ }
5179
+ this.logger.debug(`${this.name}: Flushing`);
5180
+ await this._flush();
5181
+ }
5182
+ /**
5183
+ * Hook called at the start of shutdown, before cancelling timers and aborting spans.
5184
+ * Override to perform vendor-specific pre-shutdown tasks.
5185
+ */
5186
+ async _preShutdown() {
5187
+ }
5188
+ /**
5189
+ * Hook called at the end of shutdown, after all spans are aborted.
5190
+ * Override to perform vendor-specific cleanup (e.g., flushing).
5191
+ */
5192
+ async _postShutdown() {
5193
+ }
5194
+ /**
5195
+ * Gracefully shut down the exporter.
5196
+ * Cancels all pending cleanup timers, aborts all active spans, and clears state.
5197
+ */
5198
+ async shutdown() {
5199
+ if (this.isDisabled) {
5200
+ return;
5201
+ }
5202
+ this.#shutdownStarted = true;
5203
+ await this._preShutdown();
5204
+ for (const [traceId, timeout] of this.#pendingCleanups) {
5205
+ clearTimeout(timeout);
5206
+ this.logger.debug(`${this.name}: Cancelled pending cleanup on shutdown`, { traceId });
5207
+ }
5208
+ this.#pendingCleanups.clear();
5209
+ const reason = {
5210
+ id: "SHUTDOWN",
5211
+ message: "Observability is shutting down.",
5212
+ domain: "MASTRA_OBSERVABILITY",
5213
+ category: "SYSTEM"
5214
+ };
5215
+ for (const [traceId, traceData] of this.#traceMap) {
5216
+ const orphanedEvents = traceData.getAllQueuedEvents();
5217
+ if (orphanedEvents.length > 0) {
5218
+ this.logger.warn(`${this.name}: Dropping ${orphanedEvents.length} orphaned events on shutdown`, {
5219
+ traceId,
5220
+ orphanedEvents: orphanedEvents.map((e) => ({
5221
+ spanId: e.event.exportedSpan.id,
5222
+ waitingFor: e.waitingFor,
5223
+ attempts: e.attempts
5224
+ }))
5225
+ });
5226
+ }
5227
+ for (const spanId of traceData.activeSpanIds) {
5228
+ const span = traceData.getSpan({ spanId });
5229
+ if (span) {
5230
+ await this._abortSpan({ span, traceData, reason });
5231
+ }
5232
+ }
5233
+ }
5234
+ this.#traceMap.clear();
5235
+ await this._postShutdown();
5236
+ await super.shutdown();
5237
+ }
5238
+ };
5239
+
5240
+ // src/exporters/span-formatters.ts
5241
+ function chainFormatters(formatters) {
5242
+ return async (span) => {
5243
+ let currentSpan = span;
5244
+ for (const formatter of formatters) {
5245
+ currentSpan = await formatter(currentSpan);
5246
+ }
5247
+ return currentSpan;
5248
+ };
5249
+ }
4222
5250
  var CloudExporter = class extends BaseExporter {
4223
5251
  name = "mastra-cloud-observability-exporter";
4224
- config;
5252
+ cloudConfig;
4225
5253
  buffer;
4226
5254
  flushTimer = null;
4227
5255
  constructor(config = {}) {
4228
5256
  super(config);
4229
5257
  const accessToken = config.accessToken ?? process.env.MASTRA_CLOUD_ACCESS_TOKEN;
4230
5258
  if (!accessToken) {
4231
- this.setDisabled(
4232
- "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."
4233
- );
5259
+ this.setDisabled("MASTRA_CLOUD_ACCESS_TOKEN environment variable not set.");
4234
5260
  }
4235
5261
  const endpoint = config.endpoint ?? process.env.MASTRA_CLOUD_TRACES_ENDPOINT ?? "https://api.mastra.ai/ai/spans/publish";
4236
- this.config = {
5262
+ this.cloudConfig = {
4237
5263
  logger: this.logger,
4238
5264
  logLevel: config.logLevel ?? logger.LogLevel.INFO,
4239
5265
  maxBatchSize: config.maxBatchSize ?? 1e3,
@@ -4291,12 +5317,12 @@ var CloudExporter = class extends BaseExporter {
4291
5317
  return spanRecord;
4292
5318
  }
4293
5319
  shouldFlush() {
4294
- if (this.buffer.totalSize >= this.config.maxBatchSize) {
5320
+ if (this.buffer.totalSize >= this.cloudConfig.maxBatchSize) {
4295
5321
  return true;
4296
5322
  }
4297
5323
  if (this.buffer.firstEventTime && this.buffer.totalSize > 0) {
4298
5324
  const elapsed = Date.now() - this.buffer.firstEventTime.getTime();
4299
- if (elapsed >= this.config.maxBatchWaitMs) {
5325
+ if (elapsed >= this.cloudConfig.maxBatchWaitMs) {
4300
5326
  return true;
4301
5327
  }
4302
5328
  }
@@ -4319,9 +5345,9 @@ var CloudExporter = class extends BaseExporter {
4319
5345
  this.logger.trackException(mastraError);
4320
5346
  this.logger.error("Scheduled flush failed", mastraError);
4321
5347
  });
4322
- }, this.config.maxBatchWaitMs);
5348
+ }, this.cloudConfig.maxBatchWaitMs);
4323
5349
  }
4324
- async flush() {
5350
+ async flushBuffer() {
4325
5351
  if (this.flushTimer) {
4326
5352
  clearTimeout(this.flushTimer);
4327
5353
  this.flushTimer = null;
@@ -4331,7 +5357,7 @@ var CloudExporter = class extends BaseExporter {
4331
5357
  }
4332
5358
  const startTime = Date.now();
4333
5359
  const spansCopy = [...this.buffer.spans];
4334
- const flushReason = this.buffer.totalSize >= this.config.maxBatchSize ? "size" : "time";
5360
+ const flushReason = this.buffer.totalSize >= this.cloudConfig.maxBatchSize ? "size" : "time";
4335
5361
  this.resetBuffer();
4336
5362
  try {
4337
5363
  await this.batchUpload(spansCopy);
@@ -4362,7 +5388,7 @@ var CloudExporter = class extends BaseExporter {
4362
5388
  */
4363
5389
  async batchUpload(spans) {
4364
5390
  const headers = {
4365
- Authorization: `Bearer ${this.config.accessToken}`,
5391
+ Authorization: `Bearer ${this.cloudConfig.accessToken}`,
4366
5392
  "Content-Type": "application/json"
4367
5393
  };
4368
5394
  const options = {
@@ -4370,13 +5396,29 @@ var CloudExporter = class extends BaseExporter {
4370
5396
  headers,
4371
5397
  body: JSON.stringify({ spans })
4372
5398
  };
4373
- await utils.fetchWithRetry(this.config.endpoint, options, this.config.maxRetries);
5399
+ await utils.fetchWithRetry(this.cloudConfig.endpoint, options, this.cloudConfig.maxRetries);
4374
5400
  }
4375
5401
  resetBuffer() {
4376
5402
  this.buffer.spans = [];
4377
5403
  this.buffer.firstEventTime = void 0;
4378
5404
  this.buffer.totalSize = 0;
4379
5405
  }
5406
+ /**
5407
+ * Force flush any buffered spans without shutting down the exporter.
5408
+ * This is useful in serverless environments where you need to ensure spans
5409
+ * are exported before the runtime instance is terminated.
5410
+ */
5411
+ async flush() {
5412
+ if (this.isDisabled) {
5413
+ return;
5414
+ }
5415
+ if (this.buffer.totalSize > 0) {
5416
+ this.logger.debug("Flushing buffered events", {
5417
+ bufferedEvents: this.buffer.totalSize
5418
+ });
5419
+ await this.flushBuffer();
5420
+ }
5421
+ }
4380
5422
  async shutdown() {
4381
5423
  if (this.isDisabled) {
4382
5424
  return;
@@ -4385,27 +5427,22 @@ var CloudExporter = class extends BaseExporter {
4385
5427
  clearTimeout(this.flushTimer);
4386
5428
  this.flushTimer = null;
4387
5429
  }
4388
- if (this.buffer.totalSize > 0) {
4389
- this.logger.info("Flushing remaining events on shutdown", {
4390
- remainingEvents: this.buffer.totalSize
4391
- });
4392
- try {
4393
- await this.flush();
4394
- } catch (error$1) {
4395
- const mastraError = new error.MastraError(
4396
- {
4397
- id: `CLOUD_EXPORTER_FAILED_TO_FLUSH_REMAINING_EVENTS_DURING_SHUTDOWN`,
4398
- domain: error.ErrorDomain.MASTRA_OBSERVABILITY,
4399
- category: error.ErrorCategory.USER,
4400
- details: {
4401
- remainingEvents: this.buffer.totalSize
4402
- }
4403
- },
4404
- error$1
4405
- );
4406
- this.logger.trackException(mastraError);
4407
- this.logger.error("Failed to flush remaining events during shutdown", mastraError);
4408
- }
5430
+ try {
5431
+ await this.flush();
5432
+ } catch (error$1) {
5433
+ const mastraError = new error.MastraError(
5434
+ {
5435
+ id: `CLOUD_EXPORTER_FAILED_TO_FLUSH_REMAINING_EVENTS_DURING_SHUTDOWN`,
5436
+ domain: error.ErrorDomain.MASTRA_OBSERVABILITY,
5437
+ category: error.ErrorCategory.USER,
5438
+ details: {
5439
+ remainingEvents: this.buffer.totalSize
5440
+ }
5441
+ },
5442
+ error$1
5443
+ );
5444
+ this.logger.trackException(mastraError);
5445
+ this.logger.error("Failed to flush remaining events during shutdown", mastraError);
4409
5446
  }
4410
5447
  this.logger.info("CloudExporter shutdown complete");
4411
5448
  }
@@ -4709,7 +5746,7 @@ var DefaultExporter = class extends BaseExporter {
4709
5746
  clearTimeout(this.#flushTimer);
4710
5747
  }
4711
5748
  this.#flushTimer = setTimeout(() => {
4712
- this.flush().catch((error) => {
5749
+ this.flushBuffer().catch((error) => {
4713
5750
  this.logger.error("Scheduled flush failed", {
4714
5751
  error: error instanceof Error ? error.message : String(error)
4715
5752
  });
@@ -4843,7 +5880,7 @@ var DefaultExporter = class extends BaseExporter {
4843
5880
  handleBatchWithUpdatesEvent(event) {
4844
5881
  this.addToBuffer(event);
4845
5882
  if (this.shouldFlush()) {
4846
- this.flush().catch((error) => {
5883
+ this.flushBuffer().catch((error) => {
4847
5884
  this.logger.error("Batch flush failed", {
4848
5885
  error: error instanceof Error ? error.message : String(error)
4849
5886
  });
@@ -4859,7 +5896,7 @@ var DefaultExporter = class extends BaseExporter {
4859
5896
  if (event.type === observability.TracingEventType.SPAN_ENDED) {
4860
5897
  this.addToBuffer(event);
4861
5898
  if (this.shouldFlush()) {
4862
- this.flush().catch((error) => {
5899
+ this.flushBuffer().catch((error) => {
4863
5900
  this.logger.error("Batch flush failed", {
4864
5901
  error: error instanceof Error ? error.message : String(error)
4865
5902
  });
@@ -4876,9 +5913,9 @@ var DefaultExporter = class extends BaseExporter {
4876
5913
  return this.#config.retryDelayMs * Math.pow(2, attempt);
4877
5914
  }
4878
5915
  /**
4879
- * Flushes the current buffer to storage with retry logic
5916
+ * Flushes the current buffer to storage with retry logic (internal implementation)
4880
5917
  */
4881
- async flush() {
5918
+ async flushBuffer() {
4882
5919
  if (!this.#observability) {
4883
5920
  this.logger.debug("Cannot flush traces. Observability storage is not initialized");
4884
5921
  return;
@@ -4985,23 +6022,25 @@ var DefaultExporter = class extends BaseExporter {
4985
6022
  break;
4986
6023
  }
4987
6024
  }
6025
+ /**
6026
+ * Force flush any buffered spans without shutting down the exporter.
6027
+ * This is useful in serverless environments where you need to ensure spans
6028
+ * are exported before the runtime instance is terminated.
6029
+ */
6030
+ async flush() {
6031
+ if (this.buffer.totalSize > 0) {
6032
+ this.logger.debug("Flushing buffered events", {
6033
+ bufferedEvents: this.buffer.totalSize
6034
+ });
6035
+ await this.flushBuffer();
6036
+ }
6037
+ }
4988
6038
  async shutdown() {
4989
6039
  if (this.#flushTimer) {
4990
6040
  clearTimeout(this.#flushTimer);
4991
6041
  this.#flushTimer = null;
4992
6042
  }
4993
- if (this.buffer.totalSize > 0) {
4994
- this.logger.info("Flushing remaining events on shutdown", {
4995
- remainingEvents: this.buffer.totalSize
4996
- });
4997
- try {
4998
- await this.flush();
4999
- } catch (error) {
5000
- this.logger.error("Failed to flush remaining events during shutdown", {
5001
- error: error instanceof Error ? error.message : String(error)
5002
- });
5003
- }
5004
- }
6043
+ await this.flush();
5005
6044
  this.logger.info("DefaultExporter shutdown complete");
5006
6045
  }
5007
6046
  };
@@ -5474,10 +6513,39 @@ var ModelSpanTracker = class {
5474
6513
  case "step-finish":
5475
6514
  this.#endStepSpan(chunk.payload);
5476
6515
  break;
6516
+ // Infrastructure chunks - skip creating spans for these
6517
+ // They are either redundant, metadata-only, or error/control flow
5477
6518
  case "raw":
5478
- // Skip raw chunks as they're redundant
6519
+ // Redundant raw data
5479
6520
  case "start":
6521
+ // Stream start marker
5480
6522
  case "finish":
6523
+ // Stream finish marker (step-finish already captures this)
6524
+ case "response-metadata":
6525
+ // Response metadata (not semantic content)
6526
+ case "source":
6527
+ // Source references (metadata)
6528
+ case "file":
6529
+ // Binary file data (too large/not semantic)
6530
+ case "error":
6531
+ // Error handling
6532
+ case "abort":
6533
+ // Abort signal
6534
+ case "tripwire":
6535
+ // Processor rejection
6536
+ case "watch":
6537
+ // Internal watch event
6538
+ case "tool-error":
6539
+ // Tool error handling
6540
+ case "tool-call-approval":
6541
+ // Approval request (not content)
6542
+ case "tool-call-suspended":
6543
+ // Suspension (not content)
6544
+ case "reasoning-signature":
6545
+ // Signature metadata
6546
+ case "redacted-reasoning":
6547
+ // Redacted content metadata
6548
+ case "step-output":
5481
6549
  break;
5482
6550
  case "tool-output":
5483
6551
  this.#handleToolOutputChunk(chunk);
@@ -5492,20 +6560,6 @@ var ModelSpanTracker = class {
5492
6560
  this.#createEventSpan(chunk.type, cleanPayload);
5493
6561
  break;
5494
6562
  }
5495
- // Default: auto-create event span for all other chunk types
5496
- default: {
5497
- let outputPayload = chunk.payload;
5498
- if (outputPayload && typeof outputPayload === "object" && "data" in outputPayload) {
5499
- const typedPayload = outputPayload;
5500
- outputPayload = { ...typedPayload };
5501
- if (typedPayload.data) {
5502
- outputPayload.size = typeof typedPayload.data === "string" ? typedPayload.data.length : typedPayload.data instanceof Uint8Array ? typedPayload.data.length : void 0;
5503
- delete outputPayload.data;
5504
- }
5505
- }
5506
- this.#createEventSpan(chunk.type, outputPayload);
5507
- break;
5508
- }
5509
6563
  }
5510
6564
  }
5511
6565
  })
@@ -5528,6 +6582,18 @@ var DEFAULT_DEEP_CLEAN_OPTIONS = Object.freeze({
5528
6582
  maxArrayLength: 50,
5529
6583
  maxObjectKeys: 50
5530
6584
  });
6585
+ function mergeSerializationOptions(userOptions) {
6586
+ if (!userOptions) {
6587
+ return DEFAULT_DEEP_CLEAN_OPTIONS;
6588
+ }
6589
+ return {
6590
+ keysToStrip: DEFAULT_KEYS_TO_STRIP,
6591
+ maxDepth: userOptions.maxDepth ?? DEFAULT_DEEP_CLEAN_OPTIONS.maxDepth,
6592
+ maxStringLength: userOptions.maxStringLength ?? DEFAULT_DEEP_CLEAN_OPTIONS.maxStringLength,
6593
+ maxArrayLength: userOptions.maxArrayLength ?? DEFAULT_DEEP_CLEAN_OPTIONS.maxArrayLength,
6594
+ maxObjectKeys: userOptions.maxObjectKeys ?? DEFAULT_DEEP_CLEAN_OPTIONS.maxObjectKeys
6595
+ };
6596
+ }
5531
6597
  function truncateString(s, maxChars) {
5532
6598
  if (s.length <= maxChars) {
5533
6599
  return s;
@@ -5684,25 +6750,29 @@ var BaseSpan = class {
5684
6750
  entityName;
5685
6751
  /** Parent span ID (for root spans that are children of external spans) */
5686
6752
  parentSpanId;
6753
+ /** Deep clean options for serialization */
6754
+ deepCleanOptions;
5687
6755
  constructor(options, observabilityInstance) {
6756
+ const serializationOptions = observabilityInstance.getConfig().serializationOptions;
6757
+ this.deepCleanOptions = mergeSerializationOptions(serializationOptions);
5688
6758
  this.name = options.name;
5689
6759
  this.type = options.type;
5690
- this.attributes = deepClean(options.attributes) || {};
5691
- this.metadata = deepClean(options.metadata);
6760
+ this.attributes = deepClean(options.attributes, this.deepCleanOptions) || {};
6761
+ this.metadata = deepClean(options.metadata, this.deepCleanOptions);
5692
6762
  this.parent = options.parent;
5693
- this.startTime = /* @__PURE__ */ new Date();
6763
+ this.startTime = options.startTime ?? /* @__PURE__ */ new Date();
5694
6764
  this.observabilityInstance = observabilityInstance;
5695
6765
  this.isEvent = options.isEvent ?? false;
5696
6766
  this.isInternal = isSpanInternal(this.type, options.tracingPolicy?.internal);
5697
6767
  this.traceState = options.traceState;
5698
6768
  this.tags = !options.parent && options.tags?.length ? options.tags : void 0;
5699
- this.entityType = options.entityType;
5700
- this.entityId = options.entityId;
5701
- this.entityName = options.entityName;
6769
+ this.entityType = options.entityType ?? options.parent?.entityType;
6770
+ this.entityId = options.entityId ?? options.parent?.entityId;
6771
+ this.entityName = options.entityName ?? options.parent?.entityName;
5702
6772
  if (this.isEvent) {
5703
- this.output = deepClean(options.output);
6773
+ this.output = deepClean(options.output, this.deepCleanOptions);
5704
6774
  } else {
5705
- this.input = deepClean(options.input);
6775
+ this.input = deepClean(options.input, this.deepCleanOptions);
5706
6776
  }
5707
6777
  }
5708
6778
  createChildSpan(options) {
@@ -5747,6 +6817,8 @@ var BaseSpan = class {
5747
6817
  }
5748
6818
  /** Returns a lightweight span ready for export */
5749
6819
  exportSpan(includeInternalSpans) {
6820
+ const hideInput = this.traceState?.hideInput ?? false;
6821
+ const hideOutput = this.traceState?.hideOutput ?? false;
5750
6822
  return {
5751
6823
  id: this.id,
5752
6824
  traceId: this.traceId,
@@ -5759,8 +6831,8 @@ var BaseSpan = class {
5759
6831
  metadata: this.metadata,
5760
6832
  startTime: this.startTime,
5761
6833
  endTime: this.endTime,
5762
- input: this.input,
5763
- output: this.output,
6834
+ input: hideInput ? void 0 : this.input,
6835
+ output: hideOutput ? void 0 : this.output,
5764
6836
  errorInfo: this.errorInfo,
5765
6837
  isEvent: this.isEvent,
5766
6838
  isRootSpan: this.isRootSpan,
@@ -5800,6 +6872,14 @@ var DefaultSpan = class extends BaseSpan {
5800
6872
  traceId;
5801
6873
  constructor(options, observabilityInstance) {
5802
6874
  super(options, observabilityInstance);
6875
+ if (options.spanId && options.traceId) {
6876
+ this.id = options.spanId;
6877
+ this.traceId = options.traceId;
6878
+ if (options.parentSpanId) {
6879
+ this.parentSpanId = options.parentSpanId;
6880
+ }
6881
+ return;
6882
+ }
5803
6883
  const bridge = observabilityInstance.getBridge();
5804
6884
  if (bridge && !this.isInternal) {
5805
6885
  const bridgeIds = bridge.createSpan(options);
@@ -5834,13 +6914,13 @@ var DefaultSpan = class extends BaseSpan {
5834
6914
  }
5835
6915
  this.endTime = /* @__PURE__ */ new Date();
5836
6916
  if (options?.output !== void 0) {
5837
- this.output = deepClean(options.output);
6917
+ this.output = deepClean(options.output, this.deepCleanOptions);
5838
6918
  }
5839
6919
  if (options?.attributes) {
5840
- this.attributes = { ...this.attributes, ...deepClean(options.attributes) };
6920
+ this.attributes = { ...this.attributes, ...deepClean(options.attributes, this.deepCleanOptions) };
5841
6921
  }
5842
6922
  if (options?.metadata) {
5843
- this.metadata = { ...this.metadata, ...deepClean(options.metadata) };
6923
+ this.metadata = { ...this.metadata, ...deepClean(options.metadata, this.deepCleanOptions) };
5844
6924
  }
5845
6925
  }
5846
6926
  error(options) {
@@ -5858,10 +6938,10 @@ var DefaultSpan = class extends BaseSpan {
5858
6938
  message: error$1.message
5859
6939
  };
5860
6940
  if (attributes) {
5861
- this.attributes = { ...this.attributes, ...deepClean(attributes) };
6941
+ this.attributes = { ...this.attributes, ...deepClean(attributes, this.deepCleanOptions) };
5862
6942
  }
5863
6943
  if (metadata) {
5864
- this.metadata = { ...this.metadata, ...deepClean(metadata) };
6944
+ this.metadata = { ...this.metadata, ...deepClean(metadata, this.deepCleanOptions) };
5865
6945
  }
5866
6946
  if (endSpan) {
5867
6947
  this.end();
@@ -5874,16 +6954,16 @@ var DefaultSpan = class extends BaseSpan {
5874
6954
  return;
5875
6955
  }
5876
6956
  if (options.input !== void 0) {
5877
- this.input = deepClean(options.input);
6957
+ this.input = deepClean(options.input, this.deepCleanOptions);
5878
6958
  }
5879
6959
  if (options.output !== void 0) {
5880
- this.output = deepClean(options.output);
6960
+ this.output = deepClean(options.output, this.deepCleanOptions);
5881
6961
  }
5882
6962
  if (options.attributes) {
5883
- this.attributes = { ...this.attributes, ...deepClean(options.attributes) };
6963
+ this.attributes = { ...this.attributes, ...deepClean(options.attributes, this.deepCleanOptions) };
5884
6964
  }
5885
6965
  if (options.metadata) {
5886
- this.metadata = { ...this.metadata, ...deepClean(options.metadata) };
6966
+ this.metadata = { ...this.metadata, ...deepClean(options.metadata, this.deepCleanOptions) };
5887
6967
  }
5888
6968
  }
5889
6969
  get isValid() {
@@ -5974,7 +7054,8 @@ var BaseObservabilityInstance = class extends base.MastraBase {
5974
7054
  spanOutputProcessors: config.spanOutputProcessors ?? [],
5975
7055
  bridge: config.bridge ?? void 0,
5976
7056
  includeInternalSpans: config.includeInternalSpans ?? false,
5977
- requestContextKeys: config.requestContextKeys ?? []
7057
+ requestContextKeys: config.requestContextKeys ?? [],
7058
+ serializationOptions: config.serializationOptions
5978
7059
  };
5979
7060
  if (this.config.bridge?.init) {
5980
7061
  this.config.bridge.init({ config: this.config });
@@ -6012,11 +7093,26 @@ var BaseObservabilityInstance = class extends base.MastraBase {
6012
7093
  // ============================================================================
6013
7094
  /**
6014
7095
  * Start a new span of a specific SpanType
7096
+ *
7097
+ * Sampling Decision:
7098
+ * - For root spans (no parent): Perform sampling check using the configured strategy
7099
+ * - For child spans: Inherit the sampling decision from the parent
7100
+ * - If parent is a NoOpSpan (not sampled), child is also a NoOpSpan
7101
+ * - If parent is a valid span (sampled), child is also sampled
7102
+ *
7103
+ * This ensures trace-level sampling: either all spans in a trace are sampled or none are.
7104
+ * See: https://github.com/mastra-ai/mastra/issues/11504
6015
7105
  */
6016
7106
  startSpan(options) {
6017
7107
  const { customSamplerOptions, requestContext, metadata, tracingOptions, ...rest } = options;
6018
- if (!this.shouldSample(customSamplerOptions)) {
6019
- return new NoOpSpan({ ...rest, metadata }, this);
7108
+ if (options.parent) {
7109
+ if (!options.parent.isValid) {
7110
+ return new NoOpSpan({ ...rest, metadata }, this);
7111
+ }
7112
+ } else {
7113
+ if (!this.shouldSample(customSamplerOptions)) {
7114
+ return new NoOpSpan({ ...rest, metadata }, this);
7115
+ }
6020
7116
  }
6021
7117
  let traceState;
6022
7118
  if (options.parent) {
@@ -6028,8 +7124,12 @@ var BaseObservabilityInstance = class extends base.MastraBase {
6028
7124
  const mergedMetadata = metadata || tracingMetadata ? { ...metadata, ...tracingMetadata } : void 0;
6029
7125
  const enrichedMetadata = this.extractMetadataFromRequestContext(requestContext, mergedMetadata, traceState);
6030
7126
  const tags = !options.parent ? tracingOptions?.tags : void 0;
7127
+ const traceId = !options.parent ? options.traceId ?? tracingOptions?.traceId : options.traceId;
7128
+ const parentSpanId = !options.parent ? options.parentSpanId ?? tracingOptions?.parentSpanId : options.parentSpanId;
6031
7129
  const span = this.createSpan({
6032
7130
  ...rest,
7131
+ traceId,
7132
+ parentSpanId,
6033
7133
  metadata: enrichedMetadata,
6034
7134
  traceState,
6035
7135
  tags
@@ -6042,6 +7142,37 @@ var BaseObservabilityInstance = class extends base.MastraBase {
6042
7142
  }
6043
7143
  return span;
6044
7144
  }
7145
+ /**
7146
+ * Rebuild a span from exported data for lifecycle operations.
7147
+ * Used by durable execution engines (e.g., Inngest) to end/update spans
7148
+ * that were created in a previous durable operation.
7149
+ *
7150
+ * The rebuilt span:
7151
+ * - Does NOT emit SPAN_STARTED (assumes original span already did)
7152
+ * - Can have end(), update(), error() called on it
7153
+ * - Will emit SPAN_ENDED or SPAN_UPDATED when those methods are called
7154
+ *
7155
+ * @param cached - The exported span data to rebuild from
7156
+ * @returns A span that can have lifecycle methods called on it
7157
+ */
7158
+ rebuildSpan(cached) {
7159
+ const span = this.createSpan({
7160
+ name: cached.name,
7161
+ type: cached.type,
7162
+ traceId: cached.traceId,
7163
+ spanId: cached.id,
7164
+ parentSpanId: cached.parentSpanId,
7165
+ startTime: cached.startTime instanceof Date ? cached.startTime : new Date(cached.startTime),
7166
+ input: cached.input,
7167
+ attributes: cached.attributes,
7168
+ metadata: cached.metadata,
7169
+ entityType: cached.entityType,
7170
+ entityId: cached.entityId,
7171
+ entityName: cached.entityName
7172
+ });
7173
+ this.wireSpanLifecycle(span);
7174
+ return span;
7175
+ }
6045
7176
  // ============================================================================
6046
7177
  // Configuration Management
6047
7178
  // ============================================================================
@@ -6144,11 +7275,15 @@ var BaseObservabilityInstance = class extends base.MastraBase {
6144
7275
  const configuredKeys = this.config.requestContextKeys ?? [];
6145
7276
  const additionalKeys = tracingOptions?.requestContextKeys ?? [];
6146
7277
  const allKeys = [...configuredKeys, ...additionalKeys];
6147
- if (allKeys.length === 0) {
7278
+ const hideInput = tracingOptions?.hideInput;
7279
+ const hideOutput = tracingOptions?.hideOutput;
7280
+ if (allKeys.length === 0 && !hideInput && !hideOutput) {
6148
7281
  return void 0;
6149
7282
  }
6150
7283
  return {
6151
- requestContextKeys: allKeys
7284
+ requestContextKeys: allKeys,
7285
+ ...hideInput !== void 0 && { hideInput },
7286
+ ...hideOutput !== void 0 && { hideOutput }
6152
7287
  };
6153
7288
  }
6154
7289
  /**
@@ -6279,6 +7414,29 @@ var BaseObservabilityInstance = class extends base.MastraBase {
6279
7414
  this.logger.debug(`[Observability] Initialization started [name=${this.name}]`);
6280
7415
  this.logger.info(`[Observability] Initialized successfully [name=${this.name}]`);
6281
7416
  }
7417
+ /**
7418
+ * Force flush any buffered/queued spans from all exporters and the bridge
7419
+ * without shutting down the observability instance.
7420
+ *
7421
+ * This is useful in serverless environments (like Vercel's fluid compute) where
7422
+ * you need to ensure all spans are exported before the runtime instance is
7423
+ * terminated, while keeping the observability system active for future requests.
7424
+ */
7425
+ async flush() {
7426
+ this.logger.debug(`[Observability] Flush started [name=${this.name}]`);
7427
+ const flushPromises = [...this.exporters.map((e) => e.flush())];
7428
+ if (this.config.bridge) {
7429
+ flushPromises.push(this.config.bridge.flush());
7430
+ }
7431
+ const results = await Promise.allSettled(flushPromises);
7432
+ results.forEach((result, index) => {
7433
+ if (result.status === "rejected") {
7434
+ const targetName = index < this.exporters.length ? this.exporters[index]?.name : "bridge";
7435
+ this.logger.error(`[Observability] Flush error [target=${targetName}]`, result.reason);
7436
+ }
7437
+ });
7438
+ this.logger.debug(`[Observability] Flush completed [name=${this.name}]`);
7439
+ }
6282
7440
  /**
6283
7441
  * Shutdown Observability and clean up resources
6284
7442
  */
@@ -6588,6 +7746,9 @@ var Observability = class extends base.MastraBase {
6588
7746
  }
6589
7747
  }
6590
7748
  if (config.default?.enabled) {
7749
+ console.warn(
7750
+ '[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.'
7751
+ );
6591
7752
  const defaultInstance = new DefaultObservabilityInstance({
6592
7753
  serviceName: "mastra",
6593
7754
  name: "default",
@@ -6691,13 +7852,18 @@ exports.Observability = Observability;
6691
7852
  exports.SamplingStrategyType = SamplingStrategyType;
6692
7853
  exports.SensitiveDataFilter = SensitiveDataFilter;
6693
7854
  exports.TestExporter = TestExporter;
7855
+ exports.TraceData = TraceData;
7856
+ exports.TrackingExporter = TrackingExporter;
6694
7857
  exports.buildTracingOptions = buildTracingOptions;
7858
+ exports.chainFormatters = chainFormatters;
6695
7859
  exports.deepClean = deepClean;
6696
7860
  exports.getExternalParentId = getExternalParentId;
7861
+ exports.mergeSerializationOptions = mergeSerializationOptions;
6697
7862
  exports.observabilityConfigValueSchema = observabilityConfigValueSchema;
6698
7863
  exports.observabilityInstanceConfigSchema = observabilityInstanceConfigSchema;
6699
7864
  exports.observabilityRegistryConfigSchema = observabilityRegistryConfigSchema;
6700
7865
  exports.samplingStrategySchema = samplingStrategySchema;
7866
+ exports.serializationOptionsSchema = serializationOptionsSchema;
6701
7867
  exports.truncateString = truncateString;
6702
7868
  //# sourceMappingURL=index.cjs.map
6703
7869
  //# sourceMappingURL=index.cjs.map