@mastra/observability 1.18.4 → 1.19.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3179,6 +3179,7 @@ var BaseSpan = class BaseSpan {
3179
3179
  exportSpan(includeInternalSpans) {
3180
3180
  const hideInput = this.traceState?.hideInput ?? false;
3181
3181
  const hideOutput = this.traceState?.hideOutput ?? false;
3182
+ const nestedUnderParent = this.traceState?.nestedUnderParent ?? false;
3182
3183
  return {
3183
3184
  id: this.id,
3184
3185
  traceId: this.traceId,
@@ -3200,7 +3201,8 @@ var BaseSpan = class BaseSpan {
3200
3201
  ...this.isInternal ? { isInternal: true } : {},
3201
3202
  parentSpanId: this.getParentSpanId(includeInternalSpans),
3202
3203
  externalParentSpanId: this.getExportedExternalParentSpanId(includeInternalSpans),
3203
- ...this.isRootSpan && this.tags?.length ? { tags: this.tags } : {}
3204
+ ...nestedUnderParent ? { nestedUnderParent: true } : {},
3205
+ ...this.isRootSpan && !nestedUnderParent && this.tags?.length ? { tags: this.tags } : {}
3204
3206
  };
3205
3207
  }
3206
3208
  get externalTraceId() {
@@ -3213,7 +3215,7 @@ var BaseSpan = class BaseSpan {
3213
3215
  async executeInContext(fn) {
3214
3216
  const bridge = this.observabilityInstance.getBridge();
3215
3217
  if (bridge?.executeInContext) {
3216
- const bridgeContextSpan = this.isInternal ? this.getParentSpan(false) : this;
3218
+ const bridgeContextSpan = this.isInternal || this.isExcluded ? this.getParentSpan(false) : this;
3217
3219
  return bridge.executeInContext(bridgeContextSpan?.id ?? this.id, fn);
3218
3220
  }
3219
3221
  return fn();
@@ -3225,7 +3227,7 @@ var BaseSpan = class BaseSpan {
3225
3227
  executeInContextSync(fn) {
3226
3228
  const bridge = this.observabilityInstance.getBridge();
3227
3229
  if (bridge?.executeInContextSync) {
3228
- const bridgeContextSpan = this.isInternal ? this.getParentSpan(false) : this;
3230
+ const bridgeContextSpan = this.isInternal || this.isExcluded ? this.getParentSpan(false) : this;
3229
3231
  return bridge.executeInContextSync(bridgeContextSpan?.id ?? this.id, fn);
3230
3232
  }
3231
3233
  return fn();
@@ -3270,7 +3272,7 @@ var DefaultSpan = class extends BaseSpan {
3270
3272
  return;
3271
3273
  }
3272
3274
  const bridge = observabilityInstance.getBridge();
3273
- if (bridge && !this.isInternal) {
3275
+ if (bridge && !this.isInternal && !this.isExcluded) {
3274
3276
  const bridgeIds = bridge.createSpan(options);
3275
3277
  if (bridgeIds) {
3276
3278
  this.id = bridgeIds.spanId;
@@ -4747,6 +4749,11 @@ var BaseObservabilityInstance = class extends MastraBase {
4747
4749
  tags,
4748
4750
  requestContext
4749
4751
  });
4752
+ if (!options.parent && traceState?.nestedUnderParent && (!tracingOptions?.traceId || span.traceId !== tracingOptions.traceId || !tracingOptions.parentSpanId || !(span instanceof BaseSpan) || span.externalParentSpanId !== tracingOptions.parentSpanId)) {
4753
+ const { nestedUnderParent: _ignored, ...ownTraceState } = traceState;
4754
+ span.traceState = ownTraceState;
4755
+ this.logger.debug("[Observability] Ignoring tracingOptions.nestUnderParent: the run did not join the requested trace under the requested parent");
4756
+ }
4750
4757
  if (rest.type === SpanType.MODEL_GENERATION && this.config.excludeSpanTypes?.includes(SpanType.MODEL_GENERATION)) {
4751
4758
  const attrs = rest.attributes;
4752
4759
  const model = resolveModelId(attrs?.responseModel, attrs?.model);
@@ -4782,6 +4789,7 @@ var BaseObservabilityInstance = class extends MastraBase {
4782
4789
  traceId: cached.traceId,
4783
4790
  spanId: cached.id,
4784
4791
  parentSpanId: cached.parentSpanId,
4792
+ externalParentSpanId: cached.externalParentSpanId,
4785
4793
  startTime: cached.startTime instanceof Date ? cached.startTime : new Date(cached.startTime),
4786
4794
  input: cached.input,
4787
4795
  attributes: cached.attributes,
@@ -4789,7 +4797,11 @@ var BaseObservabilityInstance = class extends MastraBase {
4789
4797
  entityType: cached.entityType,
4790
4798
  entityId: cached.entityId,
4791
4799
  entityName: cached.entityName,
4792
- tracingPolicy: cached.isInternal ? { internal: InternalSpans.ALL } : void 0
4800
+ tracingPolicy: cached.isInternal ? { internal: InternalSpans.ALL } : void 0,
4801
+ traceState: cached.nestedUnderParent ? {
4802
+ requestContextKeys: [],
4803
+ nestedUnderParent: true
4804
+ } : void 0
4793
4805
  });
4794
4806
  this.wireSpanLifecycle(span);
4795
4807
  return span;
@@ -4993,11 +5005,13 @@ var BaseObservabilityInstance = class extends MastraBase {
4993
5005
  const allKeys = [...configuredKeys, ...additionalKeys];
4994
5006
  const hideInput = tracingOptions?.hideInput;
4995
5007
  const hideOutput = tracingOptions?.hideOutput;
4996
- if (allKeys.length === 0 && !hideInput && !hideOutput) return;
5008
+ const nestedUnderParent = tracingOptions?.nestUnderParent === true;
5009
+ if (allKeys.length === 0 && !hideInput && !hideOutput && !nestedUnderParent) return;
4997
5010
  return {
4998
5011
  requestContextKeys: allKeys,
4999
5012
  ...hideInput !== void 0 && { hideInput },
5000
- ...hideOutput !== void 0 && { hideOutput }
5013
+ ...hideOutput !== void 0 && { hideOutput },
5014
+ ...nestedUnderParent && { nestedUnderParent }
5001
5015
  };
5002
5016
  }
5003
5017
  /**
@@ -5869,6 +5883,12 @@ var TraceData = class {
5869
5883
  this.#waitingForParent.delete(args.spanId);
5870
5884
  }
5871
5885
  /**
5886
+ * Get the span IDs that have events waiting on them.
5887
+ */
5888
+ getWaitingForSpanIds() {
5889
+ return [...this.#waitingForParent.keys()];
5890
+ }
5891
+ /**
5872
5892
  * Get total count of events in all waiting queues.
5873
5893
  */
5874
5894
  waitingQueueSize() {
@@ -6052,6 +6072,8 @@ var TrackingExporter = class extends BaseExporter {
6052
6072
  #hardCapEnforcementInProgress = false;
6053
6073
  /** Map of traceId to scheduled cleanup timeout */
6054
6074
  #pendingCleanups = /* @__PURE__ */ new Map();
6075
+ /** Waiting-queue replays started by setImmediate that haven't finished yet */
6076
+ #activeReplays = /* @__PURE__ */ new Set();
6055
6077
  /** Subclass configuration with resolved values */
6056
6078
  config;
6057
6079
  /** Maximum attempts to process a queued event before dropping */
@@ -6079,12 +6101,13 @@ var TrackingExporter = class extends BaseExporter {
6079
6101
  */
6080
6102
  #scheduleProcessWaitingForRoot(traceId) {
6081
6103
  setImmediate(() => {
6082
- this.#processWaitingForRoot(traceId).catch((error) => {
6104
+ if (this.#shutdownStarted) return;
6105
+ this.#trackReplay(this.#processWaitingForRoot(traceId).then(() => {}, (error) => {
6083
6106
  this.logger.error(`${this.name}: Error processing waiting-for-root queue`, {
6084
6107
  error,
6085
6108
  traceId
6086
6109
  });
6087
- });
6110
+ }));
6088
6111
  });
6089
6112
  }
6090
6113
  /**
@@ -6093,24 +6116,59 @@ var TrackingExporter = class extends BaseExporter {
6093
6116
  */
6094
6117
  #scheduleProcessWaitingFor(traceId, spanId) {
6095
6118
  setImmediate(() => {
6096
- this.#processWaitingFor(traceId, spanId).catch((error) => {
6119
+ if (this.#shutdownStarted) return;
6120
+ this.#trackReplay(this.#processWaitingFor(traceId, spanId).then(() => {}, (error) => {
6097
6121
  this.logger.error(`${this.name}: Error processing waiting queue`, {
6098
6122
  error,
6099
6123
  traceId,
6100
6124
  spanId
6101
6125
  });
6102
- });
6126
+ }));
6103
6127
  });
6104
6128
  }
6105
6129
  /**
6130
+ * Track a running replay so flush() and shutdown() can wait for it.
6131
+ */
6132
+ #trackReplay(replay) {
6133
+ this.#activeReplays.add(replay);
6134
+ replay.finally(() => this.#activeReplays.delete(replay));
6135
+ }
6136
+ /**
6137
+ * Process every queued event whose dependency has been built, without waiting for the
6138
+ * setImmediate-scheduled replays (which never fire under fake timers). Waits for replays
6139
+ * that are already running, then repeats until a pass makes no progress, so cascades
6140
+ * (root -> parent -> child -> end) are fully drained.
6141
+ */
6142
+ async #drainWaitingQueues() {
6143
+ while (true) {
6144
+ while (this.#activeReplays.size > 0) await Promise.all(this.#activeReplays);
6145
+ let progressed = false;
6146
+ for (const [traceId, traceData] of this.#traceMap) try {
6147
+ if (traceData.isRootProcessed() && await this.#processWaitingForRoot(traceId) > 0) progressed = true;
6148
+ for (const spanId of traceData.getWaitingForSpanIds()) {
6149
+ if (!traceData.hasSpan({ spanId })) continue;
6150
+ if (await this.#processWaitingFor(traceId, spanId) > 0) progressed = true;
6151
+ }
6152
+ } catch (error) {
6153
+ this.logger.error(`${this.name}: Error draining waiting queues`, {
6154
+ error,
6155
+ traceId
6156
+ });
6157
+ }
6158
+ if (!progressed && this.#activeReplays.size === 0) return;
6159
+ }
6160
+ }
6161
+ /**
6106
6162
  * Process all events waiting for root span.
6163
+ * Takes the queue before processing, so a concurrent call can't process the same events.
6164
+ * @returns The number of events that left the queue (processed, moved or dropped)
6107
6165
  */
6108
6166
  async #processWaitingForRoot(traceId) {
6109
- if (this.#shutdownStarted) return;
6110
6167
  const traceData = this.#traceMap.get(traceId);
6111
- if (!traceData) return;
6168
+ if (!traceData) return 0;
6112
6169
  const queue = traceData.getEventsWaitingForRoot();
6113
- if (queue.length === 0) return;
6170
+ if (queue.length === 0) return 0;
6171
+ traceData.clearWaitingForRoot();
6114
6172
  this.logger.debug(`${this.name}: Processing ${queue.length} events waiting for root`, { traceId });
6115
6173
  const toKeep = [];
6116
6174
  const now = Date.now();
@@ -6146,23 +6204,25 @@ var TrackingExporter = class extends BaseExporter {
6146
6204
  else toKeep.push(queuedEvent);
6147
6205
  }
6148
6206
  }
6149
- traceData.clearWaitingForRoot();
6150
6207
  for (const event of toKeep) traceData.addToWaitingQueue({
6151
6208
  event: event.event,
6152
6209
  waitingFor: "root",
6153
6210
  attempts: event.attempts,
6154
6211
  queuedAt: event.queuedAt
6155
6212
  });
6213
+ return queue.length - toKeep.length;
6156
6214
  }
6157
6215
  /**
6158
6216
  * Process events waiting for a specific parent span.
6217
+ * Takes the queue before processing, so a concurrent call can't process the same events.
6218
+ * @returns The number of events that left the queue (processed, moved or dropped)
6159
6219
  */
6160
6220
  async #processWaitingFor(traceId, spanId) {
6161
- if (this.#shutdownStarted) return;
6162
6221
  const traceData = this.#traceMap.get(traceId);
6163
- if (!traceData) return;
6222
+ if (!traceData) return 0;
6164
6223
  const queue = traceData.getEventsWaitingFor({ spanId });
6165
- if (queue.length === 0) return;
6224
+ if (queue.length === 0) return 0;
6225
+ traceData.clearWaitingFor({ spanId });
6166
6226
  this.logger.debug(`${this.name}: Processing ${queue.length} events waiting for span`, {
6167
6227
  traceId,
6168
6228
  spanId
@@ -6192,13 +6252,13 @@ var TrackingExporter = class extends BaseExporter {
6192
6252
  queuedEvent.attempts++;
6193
6253
  if (!await this.#tryProcessQueuedEvent(queuedEvent, traceData)) toKeep.push(queuedEvent);
6194
6254
  }
6195
- traceData.clearWaitingFor({ spanId });
6196
6255
  for (const event of toKeep) traceData.addToWaitingQueue({
6197
6256
  event: event.event,
6198
6257
  waitingFor: spanId,
6199
6258
  attempts: event.attempts,
6200
6259
  queuedAt: event.queuedAt
6201
6260
  });
6261
+ return queue.length - toKeep.length;
6202
6262
  }
6203
6263
  /**
6204
6264
  * Try to process a queued event.
@@ -6658,11 +6718,15 @@ var TrackingExporter = class extends BaseExporter {
6658
6718
  * This is useful in serverless environments where you need to ensure spans
6659
6719
  * are exported before the runtime instance is terminated.
6660
6720
  *
6721
+ * Queued events whose dependencies have been built are processed first, so
6722
+ * a shutdown() right after flush() only aborts spans that are still open.
6723
+ *
6661
6724
  * Subclasses should override _flush() to implement vendor-specific flush logic.
6662
6725
  */
6663
6726
  async flush() {
6664
6727
  if (this.isDisabled) return;
6665
6728
  this.logger.debug(`${this.name}: Flushing`);
6729
+ if (!this.#shutdownStarted) await this.#drainWaitingQueues();
6666
6730
  await this._flush();
6667
6731
  }
6668
6732
  /**
@@ -6682,6 +6746,7 @@ var TrackingExporter = class extends BaseExporter {
6682
6746
  async shutdown() {
6683
6747
  if (this.isDisabled) return;
6684
6748
  this.#shutdownStarted = true;
6749
+ await this.#drainWaitingQueues();
6685
6750
  await this._preShutdown();
6686
6751
  for (const [traceId, timeout] of this.#pendingCleanups) {
6687
6752
  clearTimeout(timeout);