@mastra/observability 1.18.4 → 1.19.0-alpha.1

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