@mastra/observability 1.16.6 → 1.17.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/CHANGELOG.md +30 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/index.cjs +108 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +108 -29
- package/dist/index.js.map +1 -1
- package/dist/instances/base.d.ts.map +1 -1
- package/dist/span_processors/sensitive-data-filter.d.ts +22 -3
- package/dist/span_processors/sensitive-data-filter.d.ts.map +1 -1
- package/dist/spans/base.d.ts +10 -1
- package/dist/spans/base.d.ts.map +1 -1
- package/dist/spans/default.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import path from "path";
|
|
|
10
10
|
import { TransformStream } from "stream/web";
|
|
11
11
|
import { coreFeatures } from "@mastra/core/features";
|
|
12
12
|
import { buildCreateSpanRecord, buildFeedbackRecord, buildLogRecord, buildMetricRecord, buildScoreRecord, buildUpdateSpanRecord } from "@mastra/core/storage";
|
|
13
|
+
import { createHash } from "crypto";
|
|
13
14
|
//#region src/bus/route-event.ts
|
|
14
15
|
/**
|
|
15
16
|
* Route a single event to the appropriate method on a handler.
|
|
@@ -745,7 +746,11 @@ const observabilityConfigValueSchema = z.object(observabilityInstanceConfigField
|
|
|
745
746
|
const sensitiveDataFilterOptionsSchema = z.object({
|
|
746
747
|
sensitiveFields: z.array(z.string()).optional(),
|
|
747
748
|
redactionToken: z.string().optional(),
|
|
748
|
-
redactionStyle: z.enum([
|
|
749
|
+
redactionStyle: z.enum([
|
|
750
|
+
"full",
|
|
751
|
+
"partial",
|
|
752
|
+
"indexed"
|
|
753
|
+
]).optional()
|
|
749
754
|
}).strict();
|
|
750
755
|
const observabilityRegistryConfigSchema = z.object({
|
|
751
756
|
default: z.object({ enabled: z.boolean().optional() }).optional().nullable(),
|
|
@@ -2516,8 +2521,10 @@ var BaseSpan = class {
|
|
|
2516
2521
|
entityId;
|
|
2517
2522
|
/** Entity name that created the span */
|
|
2518
2523
|
entityName;
|
|
2519
|
-
/** Parent span
|
|
2524
|
+
/** Parent span within this Mastra trace: a span Mastra created (the span-tree parent, or a prior Mastra span such as the suspended run a resume links back to) */
|
|
2520
2525
|
parentSpanId;
|
|
2526
|
+
/** 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 */
|
|
2527
|
+
externalParentSpanId;
|
|
2521
2528
|
/** Deep clean options for serialization */
|
|
2522
2529
|
deepCleanOptions;
|
|
2523
2530
|
/**
|
|
@@ -2624,6 +2631,17 @@ var BaseSpan = class {
|
|
|
2624
2631
|
if (parentSpan) return parentSpan.id;
|
|
2625
2632
|
return this.parent.getParentSpanId(includeInternalSpans);
|
|
2626
2633
|
}
|
|
2634
|
+
/**
|
|
2635
|
+
* External (foreign tracing-system) parent id to export alongside
|
|
2636
|
+
* {@link getParentSpanId}. When every Mastra ancestor is dropped from
|
|
2637
|
+
* export, the span is exported at the root's position, so the root's
|
|
2638
|
+
* external parent must travel with it.
|
|
2639
|
+
*/
|
|
2640
|
+
getExportedExternalParentSpanId(includeInternalSpans) {
|
|
2641
|
+
if (!this.parent) return this.externalParentSpanId;
|
|
2642
|
+
if (this.getParentSpan(includeInternalSpans)) return this.externalParentSpanId;
|
|
2643
|
+
return this.parent.getExportedExternalParentSpanId(includeInternalSpans);
|
|
2644
|
+
}
|
|
2627
2645
|
/** Find the closest parent span of a specific type by walking up the parent chain */
|
|
2628
2646
|
findParent(spanType) {
|
|
2629
2647
|
let current = this.parent;
|
|
@@ -2698,6 +2716,7 @@ var BaseSpan = class {
|
|
|
2698
2716
|
isEvent: this.isEvent,
|
|
2699
2717
|
isRootSpan: this.isRootSpan,
|
|
2700
2718
|
parentSpanId: this.getParentSpanId(includeInternalSpans),
|
|
2719
|
+
externalParentSpanId: this.getExportedExternalParentSpanId(includeInternalSpans),
|
|
2701
2720
|
...this.isRootSpan && this.tags?.length ? { tags: this.tags } : {}
|
|
2702
2721
|
};
|
|
2703
2722
|
}
|
|
@@ -2740,6 +2759,7 @@ var DefaultSpan = class extends BaseSpan {
|
|
|
2740
2759
|
this.id = options.spanId;
|
|
2741
2760
|
this.traceId = options.traceId;
|
|
2742
2761
|
if (options.parentSpanId) this.parentSpanId = options.parentSpanId;
|
|
2762
|
+
if (options.externalParentSpanId) this.externalParentSpanId = options.externalParentSpanId;
|
|
2743
2763
|
return;
|
|
2744
2764
|
}
|
|
2745
2765
|
const bridge = observabilityInstance.getBridge();
|
|
@@ -2748,7 +2768,8 @@ var DefaultSpan = class extends BaseSpan {
|
|
|
2748
2768
|
if (bridgeIds) {
|
|
2749
2769
|
this.id = bridgeIds.spanId;
|
|
2750
2770
|
this.traceId = bridgeIds.traceId;
|
|
2751
|
-
this.parentSpanId = bridgeIds.parentSpanId;
|
|
2771
|
+
this.parentSpanId = options.parentSpanId ?? bridgeIds.parentSpanId;
|
|
2772
|
+
this.externalParentSpanId = options.externalParentSpanId ?? bridgeIds.externalParentSpanId;
|
|
2752
2773
|
return;
|
|
2753
2774
|
}
|
|
2754
2775
|
}
|
|
@@ -2762,6 +2783,8 @@ var DefaultSpan = class extends BaseSpan {
|
|
|
2762
2783
|
this.id = generateSpanId();
|
|
2763
2784
|
if (options.parentSpanId) if (isValidSpanId(options.parentSpanId)) this.parentSpanId = options.parentSpanId;
|
|
2764
2785
|
else console.error(`[Mastra Tracing] Invalid parentSpanId: must be 1-16 hexadecimal characters, got "${options.parentSpanId}". Ignoring.`);
|
|
2786
|
+
if (options.externalParentSpanId) if (isValidSpanId(options.externalParentSpanId)) this.externalParentSpanId = options.externalParentSpanId;
|
|
2787
|
+
else console.error(`[Mastra Tracing] Invalid externalParentSpanId: must be 1-16 hexadecimal characters, got "${options.externalParentSpanId}". Ignoring.`);
|
|
2765
2788
|
}
|
|
2766
2789
|
end(options) {
|
|
2767
2790
|
if (this.isEvent) return;
|
|
@@ -3010,11 +3033,13 @@ var BaseObservabilityInstance = class extends MastraBase {
|
|
|
3010
3033
|
} : enrichedMetadata;
|
|
3011
3034
|
const tags = !options.parent ? tracingOptions?.tags : void 0;
|
|
3012
3035
|
const traceId = !options.parent ? options.traceId ?? tracingOptions?.traceId : options.traceId;
|
|
3013
|
-
const parentSpanId =
|
|
3036
|
+
const parentSpanId = options.parentSpanId;
|
|
3037
|
+
const externalParentSpanId = !options.parent ? options.externalParentSpanId ?? tracingOptions?.parentSpanId : options.externalParentSpanId;
|
|
3014
3038
|
const span = this.createSpan({
|
|
3015
3039
|
...rest,
|
|
3016
3040
|
traceId,
|
|
3017
3041
|
parentSpanId,
|
|
3042
|
+
externalParentSpanId,
|
|
3018
3043
|
metadata: finalMetadata,
|
|
3019
3044
|
traceState,
|
|
3020
3045
|
tags,
|
|
@@ -8703,13 +8728,26 @@ var ObservabilityRegistry = class {
|
|
|
8703
8728
|
//#endregion
|
|
8704
8729
|
//#region src/span_processors/sensitive-data-filter.ts
|
|
8705
8730
|
/**
|
|
8731
|
+
* Maximum number of traces to keep indexed-redaction state for.
|
|
8732
|
+
* Spans never signal trace completion, so state is evicted least-recently-used.
|
|
8733
|
+
*/
|
|
8734
|
+
const MAX_TRACKED_TRACES = 1e3;
|
|
8735
|
+
/**
|
|
8736
|
+
* Maximum number of unique values tracked per trace for indexed redaction.
|
|
8737
|
+
* Once reached, new values fall back to the full redaction token while
|
|
8738
|
+
* already-tracked values keep their assigned tokens.
|
|
8739
|
+
*/
|
|
8740
|
+
const MAX_TRACKED_VALUES_PER_TRACE = 1e3;
|
|
8741
|
+
/**
|
|
8706
8742
|
* SensitiveDataFilter
|
|
8707
8743
|
*
|
|
8708
8744
|
* An SpanOutputProcessor that redacts sensitive information from span fields.
|
|
8709
8745
|
*
|
|
8710
8746
|
* - Sensitive keys are matched case-insensitively, normalized to remove separators.
|
|
8711
|
-
* - Sensitive values are redacted using
|
|
8747
|
+
* - Sensitive values are redacted using full, partial, or indexed redaction.
|
|
8712
8748
|
* - Partial redaction always keeps 3 chars at the start and end.
|
|
8749
|
+
* - Indexed redaction assigns each unique value a stable `[LABEL_N]` token,
|
|
8750
|
+
* consistent across all spans of a trace.
|
|
8713
8751
|
* - JSON strings containing sensitive fields are parsed and redacted.
|
|
8714
8752
|
* - If filtering a field fails, the field is replaced with:
|
|
8715
8753
|
* `{ error: { processor: "sensitive-data-filter" } }`
|
|
@@ -8719,6 +8757,7 @@ var SensitiveDataFilter = class {
|
|
|
8719
8757
|
sensitiveFields;
|
|
8720
8758
|
redactionToken;
|
|
8721
8759
|
redactionStyle;
|
|
8760
|
+
traceStates = /* @__PURE__ */ new Map();
|
|
8722
8761
|
constructor(options = {}) {
|
|
8723
8762
|
this.sensitiveFields = (options.sensitiveFields || [
|
|
8724
8763
|
"password",
|
|
@@ -8748,42 +8787,63 @@ var SensitiveDataFilter = class {
|
|
|
8748
8787
|
* @returns A new span with sensitive values redacted
|
|
8749
8788
|
*/
|
|
8750
8789
|
process(span) {
|
|
8751
|
-
|
|
8752
|
-
span.
|
|
8753
|
-
span.
|
|
8754
|
-
span.
|
|
8755
|
-
span.
|
|
8790
|
+
const indexedState = this.redactionStyle === "indexed" ? this.getTraceState(span.traceId) : void 0;
|
|
8791
|
+
span.attributes = this.tryFilter(span.attributes, indexedState);
|
|
8792
|
+
span.metadata = this.tryFilter(span.metadata, indexedState);
|
|
8793
|
+
span.input = this.tryFilter(span.input, indexedState);
|
|
8794
|
+
span.output = this.tryFilter(span.output, indexedState);
|
|
8795
|
+
span.errorInfo = this.tryFilter(span.errorInfo, indexedState);
|
|
8756
8796
|
return span;
|
|
8757
8797
|
}
|
|
8758
8798
|
/**
|
|
8799
|
+
* Get (or create) the indexed-redaction state for a trace.
|
|
8800
|
+
* Uses the Map's insertion order as an LRU: accessed traces are re-inserted,
|
|
8801
|
+
* and the least recently used trace is evicted once the cap is exceeded.
|
|
8802
|
+
*/
|
|
8803
|
+
getTraceState(traceId) {
|
|
8804
|
+
let state = this.traceStates.get(traceId);
|
|
8805
|
+
if (state) this.traceStates.delete(traceId);
|
|
8806
|
+
else state = {
|
|
8807
|
+
tokensByValue: /* @__PURE__ */ new Map(),
|
|
8808
|
+
counters: /* @__PURE__ */ new Map()
|
|
8809
|
+
};
|
|
8810
|
+
this.traceStates.set(traceId, state);
|
|
8811
|
+
while (this.traceStates.size > MAX_TRACKED_TRACES) {
|
|
8812
|
+
const oldest = this.traceStates.keys().next().value;
|
|
8813
|
+
if (oldest === void 0) break;
|
|
8814
|
+
this.traceStates.delete(oldest);
|
|
8815
|
+
}
|
|
8816
|
+
return state;
|
|
8817
|
+
}
|
|
8818
|
+
/**
|
|
8759
8819
|
* Recursively filter objects/arrays for sensitive keys.
|
|
8760
8820
|
* Handles circular references by replacing with a marker.
|
|
8761
8821
|
* Also attempts to parse and redact JSON strings.
|
|
8762
8822
|
*/
|
|
8763
|
-
deepFilter(obj, seen = /* @__PURE__ */ new WeakSet()) {
|
|
8823
|
+
deepFilter(obj, seen = /* @__PURE__ */ new WeakSet(), indexedState) {
|
|
8764
8824
|
if (obj === null || typeof obj !== "object") {
|
|
8765
8825
|
if (typeof obj === "string") {
|
|
8766
8826
|
const trimmed = obj.trim();
|
|
8767
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return this.redactJsonString(obj);
|
|
8827
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return this.redactJsonString(obj, indexedState);
|
|
8768
8828
|
}
|
|
8769
8829
|
return obj;
|
|
8770
8830
|
}
|
|
8771
8831
|
if (seen.has(obj)) return "[Circular Reference]";
|
|
8772
8832
|
seen.add(obj);
|
|
8773
8833
|
if (obj instanceof Date) return obj;
|
|
8774
|
-
if (Array.isArray(obj)) return obj.map((item) => this.deepFilter(item, seen));
|
|
8834
|
+
if (Array.isArray(obj)) return obj.map((item) => this.deepFilter(item, seen, indexedState));
|
|
8775
8835
|
const filtered = {};
|
|
8776
8836
|
for (const key of Object.keys(obj)) {
|
|
8777
8837
|
const normKey = this.normalizeKey(key);
|
|
8778
|
-
if (this.isSensitive(normKey)) if (obj[key] && typeof obj[key] === "object") filtered[key] = this.deepFilter(obj[key], seen);
|
|
8779
|
-
else filtered[key] = this.redactValue(obj[key]);
|
|
8780
|
-
else filtered[key] = this.deepFilter(obj[key], seen);
|
|
8838
|
+
if (this.isSensitive(normKey)) if (obj[key] && typeof obj[key] === "object") filtered[key] = this.deepFilter(obj[key], seen, indexedState);
|
|
8839
|
+
else filtered[key] = this.redactValue(obj[key], normKey, indexedState);
|
|
8840
|
+
else filtered[key] = this.deepFilter(obj[key], seen, indexedState);
|
|
8781
8841
|
}
|
|
8782
8842
|
return filtered;
|
|
8783
8843
|
}
|
|
8784
|
-
tryFilter(value) {
|
|
8844
|
+
tryFilter(value, indexedState) {
|
|
8785
8845
|
try {
|
|
8786
|
-
return this.deepFilter(value);
|
|
8846
|
+
return this.deepFilter(value, /* @__PURE__ */ new WeakSet(), indexedState);
|
|
8787
8847
|
} catch {
|
|
8788
8848
|
return { error: { processor: this.name } };
|
|
8789
8849
|
}
|
|
@@ -8813,11 +8873,11 @@ var SensitiveDataFilter = class {
|
|
|
8813
8873
|
* Attempt to parse a string as JSON and redact sensitive fields within it.
|
|
8814
8874
|
* If parsing fails or no sensitive data is found, returns the original string.
|
|
8815
8875
|
*/
|
|
8816
|
-
redactJsonString(str) {
|
|
8876
|
+
redactJsonString(str, indexedState) {
|
|
8817
8877
|
try {
|
|
8818
8878
|
const parsed = JSON.parse(str);
|
|
8819
8879
|
if (parsed && typeof parsed === "object") {
|
|
8820
|
-
const filtered = this.deepFilter(parsed, /* @__PURE__ */ new WeakSet());
|
|
8880
|
+
const filtered = this.deepFilter(parsed, /* @__PURE__ */ new WeakSet(), indexedState);
|
|
8821
8881
|
return JSON.stringify(filtered);
|
|
8822
8882
|
}
|
|
8823
8883
|
return str;
|
|
@@ -8829,17 +8889,36 @@ var SensitiveDataFilter = class {
|
|
|
8829
8889
|
* Redact a sensitive value.
|
|
8830
8890
|
* - Full style: replaces with a fixed token.
|
|
8831
8891
|
* - Partial style: shows 3 chars at start and end, hides the middle.
|
|
8892
|
+
* - Indexed style: replaces with a stable `[LABEL_N]` token, where the label
|
|
8893
|
+
* comes from the normalized field name and the same value always maps to
|
|
8894
|
+
* the same token within a trace.
|
|
8832
8895
|
*
|
|
8833
|
-
* Non-string values are converted to strings before partial redaction.
|
|
8834
|
-
*/
|
|
8835
|
-
redactValue(value) {
|
|
8836
|
-
if (this.redactionStyle === "
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8896
|
+
* Non-string values are converted to strings before partial or indexed redaction.
|
|
8897
|
+
*/
|
|
8898
|
+
redactValue(value, normKey, indexedState) {
|
|
8899
|
+
if (this.redactionStyle === "partial") {
|
|
8900
|
+
const str = String(value);
|
|
8901
|
+
const len = str.length;
|
|
8902
|
+
if (len <= 6) return this.redactionToken;
|
|
8903
|
+
return str.slice(0, 3) + "…" + str.slice(len - 3);
|
|
8904
|
+
}
|
|
8905
|
+
if (this.redactionStyle === "indexed" && indexedState) {
|
|
8906
|
+
const valueKey = createHash("sha256").update(String(value)).digest("hex");
|
|
8907
|
+
const existing = indexedState.tokensByValue.get(valueKey);
|
|
8908
|
+
if (existing) return existing;
|
|
8909
|
+
if (indexedState.tokensByValue.size >= MAX_TRACKED_VALUES_PER_TRACE) return this.redactionToken;
|
|
8910
|
+
const label = normKey.toUpperCase();
|
|
8911
|
+
const count = (indexedState.counters.get(label) ?? 0) + 1;
|
|
8912
|
+
indexedState.counters.set(label, count);
|
|
8913
|
+
const token = `[${label}_${count}]`;
|
|
8914
|
+
indexedState.tokensByValue.set(valueKey, token);
|
|
8915
|
+
return token;
|
|
8916
|
+
}
|
|
8917
|
+
return this.redactionToken;
|
|
8918
|
+
}
|
|
8919
|
+
async shutdown() {
|
|
8920
|
+
this.traceStates.clear();
|
|
8841
8921
|
}
|
|
8842
|
-
async shutdown() {}
|
|
8843
8922
|
};
|
|
8844
8923
|
//#endregion
|
|
8845
8924
|
//#region src/default.ts
|