@mastra/observability 1.21.1-alpha.2 → 1.22.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/exporters/event-buffer.d.ts +5 -0
- package/dist/exporters/event-buffer.d.ts.map +1 -1
- package/dist/exporters/mastra-platform.d.ts.map +1 -1
- package/dist/exporters/mastra-storage.d.ts +14 -1
- package/dist/exporters/mastra-storage.d.ts.map +1 -1
- package/dist/exporters/span-previews.d.ts +4 -0
- package/dist/exporters/span-previews.d.ts.map +1 -0
- package/dist/index.cjs +136 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +136 -16
- package/dist/index.js.map +1 -1
- package/dist/instances/base.d.ts.map +1 -1
- package/dist/metrics/pricing-data.json +979 -503
- package/dist/model-tracing.d.ts +5 -0
- package/dist/model-tracing.d.ts.map +1 -1
- package/dist/spans/base.d.ts +5 -0
- package/dist/spans/base.d.ts.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createRequire } from "module";
|
|
|
2
2
|
import { MastraBase } from "@mastra/core/base";
|
|
3
3
|
import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
|
|
4
4
|
import { ConsoleLogger, LogLevel, RegisteredLogger } from "@mastra/core/logger";
|
|
5
|
+
import * as coreObservability from "@mastra/core/observability";
|
|
5
6
|
import { DEFAULT_BLOCKED_LABELS, EntityType, InternalSpans, SpanType, TracingEventType, noOpLoggerContext } from "@mastra/core/observability";
|
|
6
7
|
import { fetchWithRetry, getNestedValue, setNestedValue } from "@mastra/core/utils";
|
|
7
8
|
import { z } from "zod/v4";
|
|
@@ -2712,8 +2713,20 @@ var ModelSpanTracker = class {
|
|
|
2712
2713
|
* feature flag, or when called without an active step span. Auto-invoked from
|
|
2713
2714
|
* chunk handlers as a safety net; explicit callers get the most accurate
|
|
2714
2715
|
* start time.
|
|
2716
|
+
*
|
|
2717
|
+
* `payload.modelId` / `payload.provider` name the model the caller is about
|
|
2718
|
+
* to invoke and are recorded on MODEL_STEP, also when a fallback attempt
|
|
2719
|
+
* reuses the step's open inference span. The safety net never records them:
|
|
2720
|
+
* a replayed step-start chunk names the model of the original call.
|
|
2715
2721
|
*/
|
|
2716
2722
|
startInference(payload) {
|
|
2723
|
+
if (payload?.modelId && payload.provider) this.#currentStepSpan?.update({ attributes: {
|
|
2724
|
+
model: payload.modelId,
|
|
2725
|
+
provider: payload.provider
|
|
2726
|
+
} });
|
|
2727
|
+
this.#openInference(payload);
|
|
2728
|
+
}
|
|
2729
|
+
#openInference(payload) {
|
|
2717
2730
|
if (!supportsModelInference()) return;
|
|
2718
2731
|
if (!this.#currentStepSpan || this.#currentInferenceSpan) return;
|
|
2719
2732
|
this.#inferenceCompletionStartTime = void 0;
|
|
@@ -2838,7 +2851,7 @@ var ModelSpanTracker = class {
|
|
|
2838
2851
|
*/
|
|
2839
2852
|
#ensureStepAndInference() {
|
|
2840
2853
|
if (!this.#currentStepSpan) this.startStep();
|
|
2841
|
-
if (!this.#currentInferenceSpan) this
|
|
2854
|
+
if (!this.#currentInferenceSpan) this.#openInference();
|
|
2842
2855
|
}
|
|
2843
2856
|
/**
|
|
2844
2857
|
* Create a new chunk span (for multi-part chunks like text-start/delta/end)
|
|
@@ -3038,7 +3051,7 @@ var ModelSpanTracker = class {
|
|
|
3038
3051
|
this.#inModelResponse = true;
|
|
3039
3052
|
if (this.#currentStepSpan) this.updateStep(chunk.payload);
|
|
3040
3053
|
else this.startStep(chunk.payload);
|
|
3041
|
-
if (!this.#currentInferenceSpan) this
|
|
3054
|
+
if (!this.#currentInferenceSpan) this.#openInference(chunk.payload);
|
|
3042
3055
|
break;
|
|
3043
3056
|
case "step-finish":
|
|
3044
3057
|
this.#inModelResponse = false;
|
|
@@ -3244,6 +3257,19 @@ function getExternalParentId(options) {
|
|
|
3244
3257
|
if (parent.isInternal || parent.isExcluded) return parent.getParentSpanId(false);
|
|
3245
3258
|
return parent.id;
|
|
3246
3259
|
}
|
|
3260
|
+
/**
|
|
3261
|
+
* Vercel AI Gateway reports which provider served a step, and any provider
|
|
3262
|
+
* attempts that failed first, only in `providerMetadata.gateway.routing`.
|
|
3263
|
+
* Read it defensively so a throwing getter never skips the strip below.
|
|
3264
|
+
*/
|
|
3265
|
+
function readGatewayRouting(providerMetadata) {
|
|
3266
|
+
try {
|
|
3267
|
+
const gateway = isPlainRecord(providerMetadata) ? providerMetadata.gateway : void 0;
|
|
3268
|
+
return isPlainRecord(gateway) ? gateway.routing : void 0;
|
|
3269
|
+
} catch {
|
|
3270
|
+
return;
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3247
3273
|
var BaseSpan = class BaseSpan {
|
|
3248
3274
|
name;
|
|
3249
3275
|
type;
|
|
@@ -3273,6 +3299,11 @@ var BaseSpan = class BaseSpan {
|
|
|
3273
3299
|
parentSpanId;
|
|
3274
3300
|
/** Parent from an external tracing system (ambient OTel / dd-trace span) that Mastra did not create; carried for external correlation, not part of Mastra's own parent/child linkage */
|
|
3275
3301
|
externalParentSpanId;
|
|
3302
|
+
/** Native span fields (see `SpanData`); carried as supplied and exported only when set */
|
|
3303
|
+
resumedFromSpanId;
|
|
3304
|
+
outcome;
|
|
3305
|
+
stopReason;
|
|
3306
|
+
stopReasonContext;
|
|
3276
3307
|
/** Deep clean options for serialization */
|
|
3277
3308
|
deepCleanOptions;
|
|
3278
3309
|
/**
|
|
@@ -3339,6 +3370,10 @@ var BaseSpan = class BaseSpan {
|
|
|
3339
3370
|
}
|
|
3340
3371
|
this.tags = !options.parent && options.tags?.length ? options.tags : void 0;
|
|
3341
3372
|
this.links = validLinks(options.links);
|
|
3373
|
+
this.resumedFromSpanId = options.resumedFromSpanId;
|
|
3374
|
+
this.outcome = options.outcome;
|
|
3375
|
+
this.stopReason = options.stopReason;
|
|
3376
|
+
this.stopReasonContext = deepClean(options.stopReasonContext, this.deepCleanOptions);
|
|
3342
3377
|
const entityParent = this.getParentSpan(false);
|
|
3343
3378
|
this.entityType = options.entityType ?? entityParent?.entityType;
|
|
3344
3379
|
this.entityId = options.entityId ?? entityParent?.entityId;
|
|
@@ -3371,8 +3406,10 @@ var BaseSpan = class BaseSpan {
|
|
|
3371
3406
|
if (this.type !== SpanType.MODEL_STEP) return value;
|
|
3372
3407
|
try {
|
|
3373
3408
|
const prepared = { ...value };
|
|
3409
|
+
const gatewayRouting = readGatewayRouting(prepared.providerMetadata);
|
|
3374
3410
|
delete prepared.providerMetadata;
|
|
3375
3411
|
delete prepared.experimental_providerMetadata;
|
|
3412
|
+
if (gatewayRouting !== void 0) prepared.providerMetadata = { gateway: { routing: gatewayRouting } };
|
|
3376
3413
|
return prepared;
|
|
3377
3414
|
} catch {
|
|
3378
3415
|
return value;
|
|
@@ -3605,6 +3642,10 @@ var BaseSpan = class BaseSpan {
|
|
|
3605
3642
|
...definedLineage(correlation),
|
|
3606
3643
|
parentSpanId: this.getParentSpanId(includeInternalSpans),
|
|
3607
3644
|
externalParentSpanId: this.getExportedExternalParentSpanId(includeInternalSpans),
|
|
3645
|
+
...this.resumedFromSpanId !== void 0 ? { resumedFromSpanId: this.resumedFromSpanId } : {},
|
|
3646
|
+
...this.outcome !== void 0 ? { outcome: this.outcome } : {},
|
|
3647
|
+
...this.stopReason !== void 0 ? { stopReason: this.stopReason } : {},
|
|
3648
|
+
...this.stopReasonContext !== void 0 ? { stopReasonContext: this.stopReasonContext } : {},
|
|
3608
3649
|
...nestedUnderParent ? { nestedUnderParent: true } : {},
|
|
3609
3650
|
...links ? { links } : {},
|
|
3610
3651
|
...this.isRootSpan && !nestedUnderParent && this.tags?.length ? { tags: this.tags } : {}
|
|
@@ -3833,7 +3874,7 @@ function isValidSpanId(spanId) {
|
|
|
3833
3874
|
function getOrCreateTraceId(options) {
|
|
3834
3875
|
if (options.traceId) {
|
|
3835
3876
|
if (isValidTraceId(options.traceId)) return options.traceId;
|
|
3836
|
-
|
|
3877
|
+
console.error(`[Mastra Tracing] Invalid traceId: must be 1-32 hexadecimal characters, got "${options.traceId}". Generating new trace ID.`);
|
|
3837
3878
|
}
|
|
3838
3879
|
return generateTraceId();
|
|
3839
3880
|
}
|
|
@@ -4029,6 +4070,8 @@ var EventBuffer = class {
|
|
|
4029
4070
|
#creates = [];
|
|
4030
4071
|
#updates = [];
|
|
4031
4072
|
#allCreatedSpans = /* @__PURE__ */ new Set();
|
|
4073
|
+
/** Spans whose SPAN_STARTED this buffer has seen but whose create isn't confirmed yet */
|
|
4074
|
+
#pendingCreateSpans = /* @__PURE__ */ new Set();
|
|
4032
4075
|
#firstEventTime;
|
|
4033
4076
|
#storageStrategy;
|
|
4034
4077
|
#maxRetries;
|
|
@@ -4079,8 +4122,11 @@ var EventBuffer = class {
|
|
|
4079
4122
|
case TracingEventType.SPAN_STARTED:
|
|
4080
4123
|
switch (this.#storageStrategy) {
|
|
4081
4124
|
case "realtime":
|
|
4082
|
-
case "
|
|
4083
|
-
|
|
4125
|
+
case "batch-with-updates":
|
|
4126
|
+
if (!event.exportedSpan.isEvent) this.#pendingCreateSpans.add(this.exportedSpanKey(event.exportedSpan));
|
|
4127
|
+
this.pushCreate(event);
|
|
4128
|
+
break;
|
|
4129
|
+
case "event-sourced": this.pushCreate(event);
|
|
4084
4130
|
}
|
|
4085
4131
|
break;
|
|
4086
4132
|
case TracingEventType.SPAN_UPDATED:
|
|
@@ -4149,23 +4195,40 @@ var EventBuffer = class {
|
|
|
4149
4195
|
buildSpanKey(span) {
|
|
4150
4196
|
return `${span.traceId}:${span.spanId}`;
|
|
4151
4197
|
}
|
|
4198
|
+
exportedSpanKey(span) {
|
|
4199
|
+
return this.buildSpanKey({
|
|
4200
|
+
traceId: span.traceId,
|
|
4201
|
+
spanId: span.id
|
|
4202
|
+
});
|
|
4203
|
+
}
|
|
4152
4204
|
/** Track successfully created spans so updates can verify span existence before flushing. */
|
|
4153
4205
|
addCreatedSpans(args) {
|
|
4154
4206
|
if (this.#storageStrategy === "event-sourced" || this.#storageStrategy === "insert-only") return;
|
|
4155
|
-
for (const createRecord of args.records) if (!createRecord.isEvent)
|
|
4207
|
+
for (const createRecord of args.records) if (!createRecord.isEvent) {
|
|
4208
|
+
const key = this.buildSpanKey(createRecord);
|
|
4209
|
+
this.#pendingCreateSpans.delete(key);
|
|
4210
|
+
this.#allCreatedSpans.add(key);
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
/** Stop waiting on creates that were dropped, so their updates aren't held back for them. */
|
|
4214
|
+
forgetDroppedCreates(events) {
|
|
4215
|
+
for (const event of events) if (event.type === TracingEventType.SPAN_STARTED) this.#pendingCreateSpans.delete(this.exportedSpanKey(event.exportedSpan));
|
|
4156
4216
|
}
|
|
4157
4217
|
/** Check whether a span's create record has already been flushed to storage. */
|
|
4158
4218
|
spanExists(span) {
|
|
4159
|
-
return this.#allCreatedSpans
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4219
|
+
return this.#allCreatedSpans.has(this.exportedSpanKey(span));
|
|
4220
|
+
}
|
|
4221
|
+
/** Check whether this buffer saw the span's SPAN_STARTED but its create hasn't been confirmed yet. */
|
|
4222
|
+
isAwaitingCreate(span) {
|
|
4223
|
+
return this.#pendingCreateSpans.has(this.exportedSpanKey(span));
|
|
4163
4224
|
}
|
|
4164
4225
|
/** Remove completed spans from tracking after their SPAN_ENDED updates are flushed. */
|
|
4165
4226
|
endFinishedSpans(args) {
|
|
4166
4227
|
if (this.#storageStrategy === "event-sourced" || this.#storageStrategy === "insert-only") return;
|
|
4167
4228
|
args.records.forEach((r) => {
|
|
4168
|
-
this
|
|
4229
|
+
const key = this.buildSpanKey(r);
|
|
4230
|
+
this.#allCreatedSpans.delete(key);
|
|
4231
|
+
this.#pendingCreateSpans.delete(key);
|
|
4169
4232
|
});
|
|
4170
4233
|
}
|
|
4171
4234
|
};
|
|
@@ -4607,6 +4670,21 @@ var DefaultExporter = class extends BaseExporter {
|
|
|
4607
4670
|
this.logger.info("DefaultExporter shutdown complete");
|
|
4608
4671
|
}
|
|
4609
4672
|
};
|
|
4673
|
+
// src/exporters/span-previews.ts
|
|
4674
|
+
const buildSpanPreviews = coreObservability.buildSpanPreviews;
|
|
4675
|
+
/** Older core versions retain their existing storage preview fallback. */
|
|
4676
|
+
function withSpanPreviews(span) {
|
|
4677
|
+
if (!coreFeatures.has("observability-span-previews")) return span;
|
|
4678
|
+
try {
|
|
4679
|
+
const previews = buildSpanPreviews?.(span);
|
|
4680
|
+
return previews ? {
|
|
4681
|
+
...span,
|
|
4682
|
+
...previews
|
|
4683
|
+
} : span;
|
|
4684
|
+
} catch {
|
|
4685
|
+
return span;
|
|
4686
|
+
}
|
|
4687
|
+
}
|
|
4610
4688
|
// src/exporters/mastra-storage.ts
|
|
4611
4689
|
/**
|
|
4612
4690
|
* Resolves the final tracing storage strategy based on config and observability store hints
|
|
@@ -4810,6 +4888,7 @@ var MastraStorageExporter = class extends BaseExporter {
|
|
|
4810
4888
|
return { failed: true };
|
|
4811
4889
|
}
|
|
4812
4890
|
const dropped = this.#eventBuffer.reAddCreates(events);
|
|
4891
|
+
this.#eventBuffer.forgetDroppedCreates(dropped);
|
|
4813
4892
|
const retryAttempt = this.retryAttempt(events);
|
|
4814
4893
|
this.logStorageFailure(signal, events, retryAttempt, error);
|
|
4815
4894
|
this.emitDrop(signal, "retry-exhausted", dropped.length, error);
|
|
@@ -4820,7 +4899,15 @@ var MastraStorageExporter = class extends BaseExporter {
|
|
|
4820
4899
|
}
|
|
4821
4900
|
}
|
|
4822
4901
|
/**
|
|
4823
|
-
* Flush span update/end events
|
|
4902
|
+
* Flush span update/end events.
|
|
4903
|
+
*
|
|
4904
|
+
* When the store supports conditional updates, only spans this exporter started
|
|
4905
|
+
* and hasn't confirmed the create for are deferred. Everything else, including
|
|
4906
|
+
* spans another exporter instance started (such as a durable agent ending spans
|
|
4907
|
+
* it rebuilt after a crash), goes straight to storage, and updates that didn't
|
|
4908
|
+
* apply are counted as drops. Without conditional updates, updates for spans this
|
|
4909
|
+
* exporter hasn't created are deferred until their create is flushed.
|
|
4910
|
+
*
|
|
4824
4911
|
* When `isEnd` is true, successfully flushed spans are removed from tracking.
|
|
4825
4912
|
*/
|
|
4826
4913
|
async flushSpanUpdates(events, deferredUpdates, isEnd) {
|
|
@@ -4830,10 +4917,12 @@ var MastraStorageExporter = class extends BaseExporter {
|
|
|
4830
4917
|
this.emitDrop("tracing", "unsupported-storage", events.length);
|
|
4831
4918
|
return { failed: true };
|
|
4832
4919
|
}
|
|
4920
|
+
const storage = this.#observabilityStorage;
|
|
4921
|
+
const batchUpdateUnendedSpans = typeof storage.batchUpdateUnendedSpans === "function" ? storage.batchUpdateUnendedSpans.bind(storage) : void 0;
|
|
4833
4922
|
const partials = [];
|
|
4834
4923
|
for (const event of events) {
|
|
4835
4924
|
const span = event.exportedSpan;
|
|
4836
|
-
if (this.#eventBuffer.spanExists(span)) partials.push({
|
|
4925
|
+
if (batchUpdateUnendedSpans ? !this.#eventBuffer.isAwaitingCreate(span) : this.#eventBuffer.spanExists(span)) partials.push({
|
|
4837
4926
|
traceId: span.traceId,
|
|
4838
4927
|
spanId: span.id,
|
|
4839
4928
|
updates: buildUpdateSpanRecord(span)
|
|
@@ -4842,7 +4931,10 @@ var MastraStorageExporter = class extends BaseExporter {
|
|
|
4842
4931
|
}
|
|
4843
4932
|
if (partials.length === 0) return FLUSH_SUCCEEDED;
|
|
4844
4933
|
try {
|
|
4845
|
-
|
|
4934
|
+
if (batchUpdateUnendedSpans) {
|
|
4935
|
+
const { updated } = await batchUpdateUnendedSpans({ records: partials });
|
|
4936
|
+
this.emitNotApplied(partials, updated);
|
|
4937
|
+
} else await storage.batchUpdateSpans({ records: partials });
|
|
4846
4938
|
if (isEnd) this.#eventBuffer.endFinishedSpans({ records: partials });
|
|
4847
4939
|
return FLUSH_SUCCEEDED;
|
|
4848
4940
|
} catch (error) {
|
|
@@ -4866,6 +4958,27 @@ var MastraStorageExporter = class extends BaseExporter {
|
|
|
4866
4958
|
}
|
|
4867
4959
|
}
|
|
4868
4960
|
/**
|
|
4961
|
+
* Reports conditional span updates that storage didn't apply: the span wasn't
|
|
4962
|
+
* stored, or it had already ended. Ended spans are immutable, so these are dropped.
|
|
4963
|
+
*/
|
|
4964
|
+
emitNotApplied(partials, updated) {
|
|
4965
|
+
const appliedCounts = /* @__PURE__ */ new Map();
|
|
4966
|
+
for (const span of updated) {
|
|
4967
|
+
const key = `${span.traceId}:${span.spanId}`;
|
|
4968
|
+
appliedCounts.set(key, (appliedCounts.get(key) ?? 0) + 1);
|
|
4969
|
+
}
|
|
4970
|
+
let notApplied = 0;
|
|
4971
|
+
for (const partial of partials) {
|
|
4972
|
+
const key = `${partial.traceId}:${partial.spanId}`;
|
|
4973
|
+
const remaining = appliedCounts.get(key) ?? 0;
|
|
4974
|
+
if (remaining > 0) appliedCounts.set(key, remaining - 1);
|
|
4975
|
+
else notApplied++;
|
|
4976
|
+
}
|
|
4977
|
+
if (notApplied === 0) return;
|
|
4978
|
+
this.logger.debug("Span updates not applied: span not stored or already ended", { count: notApplied });
|
|
4979
|
+
this.emitDrop("tracing", "span-not-updatable", notApplied);
|
|
4980
|
+
}
|
|
4981
|
+
/**
|
|
4869
4982
|
* Runs flushBuffer() and tracks its promise so flush() can wait for writes
|
|
4870
4983
|
* that a timer or size trigger started on their own.
|
|
4871
4984
|
*/
|
|
@@ -4975,7 +5088,10 @@ var MastraStorageExporter = class extends BaseExporter {
|
|
|
4975
5088
|
this.logger.debug("Cannot store traces. Observability storage is not initialized");
|
|
4976
5089
|
return;
|
|
4977
5090
|
}
|
|
4978
|
-
this.#eventBuffer.addEvent(
|
|
5091
|
+
this.#eventBuffer.addEvent({
|
|
5092
|
+
...event,
|
|
5093
|
+
exportedSpan: withSpanPreviews(event.exportedSpan)
|
|
5094
|
+
});
|
|
4979
5095
|
await this.handleBatchedFlush();
|
|
4980
5096
|
}
|
|
4981
5097
|
/**
|
|
@@ -5227,6 +5343,10 @@ var BaseObservabilityInstance = class extends MastraBase {
|
|
|
5227
5343
|
spanId: cached.id,
|
|
5228
5344
|
parentSpanId: cached.parentSpanId,
|
|
5229
5345
|
externalParentSpanId: cached.externalParentSpanId,
|
|
5346
|
+
resumedFromSpanId: cached.resumedFromSpanId,
|
|
5347
|
+
outcome: cached.outcome,
|
|
5348
|
+
stopReason: cached.stopReason,
|
|
5349
|
+
stopReasonContext: cached.stopReasonContext,
|
|
5230
5350
|
links: cached.links,
|
|
5231
5351
|
startTime: cached.startTime instanceof Date ? cached.startTime : new Date(cached.startTime),
|
|
5232
5352
|
input: cached.input,
|
|
@@ -8079,7 +8199,7 @@ var MastraPlatformExporter = class extends BaseExporter {
|
|
|
8079
8199
|
}
|
|
8080
8200
|
formatSpan(span) {
|
|
8081
8201
|
return {
|
|
8082
|
-
...span,
|
|
8202
|
+
...withSpanPreviews(span),
|
|
8083
8203
|
spanId: span.id,
|
|
8084
8204
|
spanType: span.type,
|
|
8085
8205
|
startedAt: span.startTime,
|