@mastra/observability 1.21.1-alpha.2 → 1.22.0-alpha.3
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 +118 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +118 -15
- package/dist/index.js.map +1 -1
- package/dist/metrics/pricing-data.json +640 -222
- package/dist/model-tracing.d.ts +5 -0
- package/dist/model-tracing.d.ts.map +1 -1
- package/dist/spans/base.d.ts.map +1 -1
- package/package.json +2 -2
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;
|
|
@@ -3371,8 +3397,10 @@ var BaseSpan = class BaseSpan {
|
|
|
3371
3397
|
if (this.type !== SpanType.MODEL_STEP) return value;
|
|
3372
3398
|
try {
|
|
3373
3399
|
const prepared = { ...value };
|
|
3400
|
+
const gatewayRouting = readGatewayRouting(prepared.providerMetadata);
|
|
3374
3401
|
delete prepared.providerMetadata;
|
|
3375
3402
|
delete prepared.experimental_providerMetadata;
|
|
3403
|
+
if (gatewayRouting !== void 0) prepared.providerMetadata = { gateway: { routing: gatewayRouting } };
|
|
3376
3404
|
return prepared;
|
|
3377
3405
|
} catch {
|
|
3378
3406
|
return value;
|
|
@@ -4029,6 +4057,8 @@ var EventBuffer = class {
|
|
|
4029
4057
|
#creates = [];
|
|
4030
4058
|
#updates = [];
|
|
4031
4059
|
#allCreatedSpans = /* @__PURE__ */ new Set();
|
|
4060
|
+
/** Spans whose SPAN_STARTED this buffer has seen but whose create isn't confirmed yet */
|
|
4061
|
+
#pendingCreateSpans = /* @__PURE__ */ new Set();
|
|
4032
4062
|
#firstEventTime;
|
|
4033
4063
|
#storageStrategy;
|
|
4034
4064
|
#maxRetries;
|
|
@@ -4079,8 +4109,11 @@ var EventBuffer = class {
|
|
|
4079
4109
|
case TracingEventType.SPAN_STARTED:
|
|
4080
4110
|
switch (this.#storageStrategy) {
|
|
4081
4111
|
case "realtime":
|
|
4082
|
-
case "
|
|
4083
|
-
|
|
4112
|
+
case "batch-with-updates":
|
|
4113
|
+
if (!event.exportedSpan.isEvent) this.#pendingCreateSpans.add(this.exportedSpanKey(event.exportedSpan));
|
|
4114
|
+
this.pushCreate(event);
|
|
4115
|
+
break;
|
|
4116
|
+
case "event-sourced": this.pushCreate(event);
|
|
4084
4117
|
}
|
|
4085
4118
|
break;
|
|
4086
4119
|
case TracingEventType.SPAN_UPDATED:
|
|
@@ -4149,23 +4182,40 @@ var EventBuffer = class {
|
|
|
4149
4182
|
buildSpanKey(span) {
|
|
4150
4183
|
return `${span.traceId}:${span.spanId}`;
|
|
4151
4184
|
}
|
|
4185
|
+
exportedSpanKey(span) {
|
|
4186
|
+
return this.buildSpanKey({
|
|
4187
|
+
traceId: span.traceId,
|
|
4188
|
+
spanId: span.id
|
|
4189
|
+
});
|
|
4190
|
+
}
|
|
4152
4191
|
/** Track successfully created spans so updates can verify span existence before flushing. */
|
|
4153
4192
|
addCreatedSpans(args) {
|
|
4154
4193
|
if (this.#storageStrategy === "event-sourced" || this.#storageStrategy === "insert-only") return;
|
|
4155
|
-
for (const createRecord of args.records) if (!createRecord.isEvent)
|
|
4194
|
+
for (const createRecord of args.records) if (!createRecord.isEvent) {
|
|
4195
|
+
const key = this.buildSpanKey(createRecord);
|
|
4196
|
+
this.#pendingCreateSpans.delete(key);
|
|
4197
|
+
this.#allCreatedSpans.add(key);
|
|
4198
|
+
}
|
|
4199
|
+
}
|
|
4200
|
+
/** Stop waiting on creates that were dropped, so their updates aren't held back for them. */
|
|
4201
|
+
forgetDroppedCreates(events) {
|
|
4202
|
+
for (const event of events) if (event.type === TracingEventType.SPAN_STARTED) this.#pendingCreateSpans.delete(this.exportedSpanKey(event.exportedSpan));
|
|
4156
4203
|
}
|
|
4157
4204
|
/** Check whether a span's create record has already been flushed to storage. */
|
|
4158
4205
|
spanExists(span) {
|
|
4159
|
-
return this.#allCreatedSpans
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4206
|
+
return this.#allCreatedSpans.has(this.exportedSpanKey(span));
|
|
4207
|
+
}
|
|
4208
|
+
/** Check whether this buffer saw the span's SPAN_STARTED but its create hasn't been confirmed yet. */
|
|
4209
|
+
isAwaitingCreate(span) {
|
|
4210
|
+
return this.#pendingCreateSpans.has(this.exportedSpanKey(span));
|
|
4163
4211
|
}
|
|
4164
4212
|
/** Remove completed spans from tracking after their SPAN_ENDED updates are flushed. */
|
|
4165
4213
|
endFinishedSpans(args) {
|
|
4166
4214
|
if (this.#storageStrategy === "event-sourced" || this.#storageStrategy === "insert-only") return;
|
|
4167
4215
|
args.records.forEach((r) => {
|
|
4168
|
-
this
|
|
4216
|
+
const key = this.buildSpanKey(r);
|
|
4217
|
+
this.#allCreatedSpans.delete(key);
|
|
4218
|
+
this.#pendingCreateSpans.delete(key);
|
|
4169
4219
|
});
|
|
4170
4220
|
}
|
|
4171
4221
|
};
|
|
@@ -4607,6 +4657,21 @@ var DefaultExporter = class extends BaseExporter {
|
|
|
4607
4657
|
this.logger.info("DefaultExporter shutdown complete");
|
|
4608
4658
|
}
|
|
4609
4659
|
};
|
|
4660
|
+
// src/exporters/span-previews.ts
|
|
4661
|
+
const buildSpanPreviews = coreObservability.buildSpanPreviews;
|
|
4662
|
+
/** Older core versions retain their existing storage preview fallback. */
|
|
4663
|
+
function withSpanPreviews(span) {
|
|
4664
|
+
if (!coreFeatures.has("observability-span-previews")) return span;
|
|
4665
|
+
try {
|
|
4666
|
+
const previews = buildSpanPreviews?.(span);
|
|
4667
|
+
return previews ? {
|
|
4668
|
+
...span,
|
|
4669
|
+
...previews
|
|
4670
|
+
} : span;
|
|
4671
|
+
} catch {
|
|
4672
|
+
return span;
|
|
4673
|
+
}
|
|
4674
|
+
}
|
|
4610
4675
|
// src/exporters/mastra-storage.ts
|
|
4611
4676
|
/**
|
|
4612
4677
|
* Resolves the final tracing storage strategy based on config and observability store hints
|
|
@@ -4810,6 +4875,7 @@ var MastraStorageExporter = class extends BaseExporter {
|
|
|
4810
4875
|
return { failed: true };
|
|
4811
4876
|
}
|
|
4812
4877
|
const dropped = this.#eventBuffer.reAddCreates(events);
|
|
4878
|
+
this.#eventBuffer.forgetDroppedCreates(dropped);
|
|
4813
4879
|
const retryAttempt = this.retryAttempt(events);
|
|
4814
4880
|
this.logStorageFailure(signal, events, retryAttempt, error);
|
|
4815
4881
|
this.emitDrop(signal, "retry-exhausted", dropped.length, error);
|
|
@@ -4820,7 +4886,15 @@ var MastraStorageExporter = class extends BaseExporter {
|
|
|
4820
4886
|
}
|
|
4821
4887
|
}
|
|
4822
4888
|
/**
|
|
4823
|
-
* Flush span update/end events
|
|
4889
|
+
* Flush span update/end events.
|
|
4890
|
+
*
|
|
4891
|
+
* When the store supports conditional updates, only spans this exporter started
|
|
4892
|
+
* and hasn't confirmed the create for are deferred. Everything else, including
|
|
4893
|
+
* spans another exporter instance started (such as a durable agent ending spans
|
|
4894
|
+
* it rebuilt after a crash), goes straight to storage, and updates that didn't
|
|
4895
|
+
* apply are counted as drops. Without conditional updates, updates for spans this
|
|
4896
|
+
* exporter hasn't created are deferred until their create is flushed.
|
|
4897
|
+
*
|
|
4824
4898
|
* When `isEnd` is true, successfully flushed spans are removed from tracking.
|
|
4825
4899
|
*/
|
|
4826
4900
|
async flushSpanUpdates(events, deferredUpdates, isEnd) {
|
|
@@ -4830,10 +4904,12 @@ var MastraStorageExporter = class extends BaseExporter {
|
|
|
4830
4904
|
this.emitDrop("tracing", "unsupported-storage", events.length);
|
|
4831
4905
|
return { failed: true };
|
|
4832
4906
|
}
|
|
4907
|
+
const storage = this.#observabilityStorage;
|
|
4908
|
+
const batchUpdateUnendedSpans = typeof storage.batchUpdateUnendedSpans === "function" ? storage.batchUpdateUnendedSpans.bind(storage) : void 0;
|
|
4833
4909
|
const partials = [];
|
|
4834
4910
|
for (const event of events) {
|
|
4835
4911
|
const span = event.exportedSpan;
|
|
4836
|
-
if (this.#eventBuffer.spanExists(span)) partials.push({
|
|
4912
|
+
if (batchUpdateUnendedSpans ? !this.#eventBuffer.isAwaitingCreate(span) : this.#eventBuffer.spanExists(span)) partials.push({
|
|
4837
4913
|
traceId: span.traceId,
|
|
4838
4914
|
spanId: span.id,
|
|
4839
4915
|
updates: buildUpdateSpanRecord(span)
|
|
@@ -4842,7 +4918,10 @@ var MastraStorageExporter = class extends BaseExporter {
|
|
|
4842
4918
|
}
|
|
4843
4919
|
if (partials.length === 0) return FLUSH_SUCCEEDED;
|
|
4844
4920
|
try {
|
|
4845
|
-
|
|
4921
|
+
if (batchUpdateUnendedSpans) {
|
|
4922
|
+
const { updated } = await batchUpdateUnendedSpans({ records: partials });
|
|
4923
|
+
this.emitNotApplied(partials, updated);
|
|
4924
|
+
} else await storage.batchUpdateSpans({ records: partials });
|
|
4846
4925
|
if (isEnd) this.#eventBuffer.endFinishedSpans({ records: partials });
|
|
4847
4926
|
return FLUSH_SUCCEEDED;
|
|
4848
4927
|
} catch (error) {
|
|
@@ -4866,6 +4945,27 @@ var MastraStorageExporter = class extends BaseExporter {
|
|
|
4866
4945
|
}
|
|
4867
4946
|
}
|
|
4868
4947
|
/**
|
|
4948
|
+
* Reports conditional span updates that storage didn't apply: the span wasn't
|
|
4949
|
+
* stored, or it had already ended. Ended spans are immutable, so these are dropped.
|
|
4950
|
+
*/
|
|
4951
|
+
emitNotApplied(partials, updated) {
|
|
4952
|
+
const appliedCounts = /* @__PURE__ */ new Map();
|
|
4953
|
+
for (const span of updated) {
|
|
4954
|
+
const key = `${span.traceId}:${span.spanId}`;
|
|
4955
|
+
appliedCounts.set(key, (appliedCounts.get(key) ?? 0) + 1);
|
|
4956
|
+
}
|
|
4957
|
+
let notApplied = 0;
|
|
4958
|
+
for (const partial of partials) {
|
|
4959
|
+
const key = `${partial.traceId}:${partial.spanId}`;
|
|
4960
|
+
const remaining = appliedCounts.get(key) ?? 0;
|
|
4961
|
+
if (remaining > 0) appliedCounts.set(key, remaining - 1);
|
|
4962
|
+
else notApplied++;
|
|
4963
|
+
}
|
|
4964
|
+
if (notApplied === 0) return;
|
|
4965
|
+
this.logger.debug("Span updates not applied: span not stored or already ended", { count: notApplied });
|
|
4966
|
+
this.emitDrop("tracing", "span-not-updatable", notApplied);
|
|
4967
|
+
}
|
|
4968
|
+
/**
|
|
4869
4969
|
* Runs flushBuffer() and tracks its promise so flush() can wait for writes
|
|
4870
4970
|
* that a timer or size trigger started on their own.
|
|
4871
4971
|
*/
|
|
@@ -4975,7 +5075,10 @@ var MastraStorageExporter = class extends BaseExporter {
|
|
|
4975
5075
|
this.logger.debug("Cannot store traces. Observability storage is not initialized");
|
|
4976
5076
|
return;
|
|
4977
5077
|
}
|
|
4978
|
-
this.#eventBuffer.addEvent(
|
|
5078
|
+
this.#eventBuffer.addEvent({
|
|
5079
|
+
...event,
|
|
5080
|
+
exportedSpan: withSpanPreviews(event.exportedSpan)
|
|
5081
|
+
});
|
|
4979
5082
|
await this.handleBatchedFlush();
|
|
4980
5083
|
}
|
|
4981
5084
|
/**
|
|
@@ -8079,7 +8182,7 @@ var MastraPlatformExporter = class extends BaseExporter {
|
|
|
8079
8182
|
}
|
|
8080
8183
|
formatSpan(span) {
|
|
8081
8184
|
return {
|
|
8082
|
-
...span,
|
|
8185
|
+
...withSpanPreviews(span),
|
|
8083
8186
|
spanId: span.id,
|
|
8084
8187
|
spanType: span.type,
|
|
8085
8188
|
startedAt: span.startTime,
|