@mastra/observability 1.17.0-alpha.0 → 1.17.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/CHANGELOG.md +40 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/index.cjs +85 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +85 -26
- package/dist/index.js.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/package.json +6 -6
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(),
|
|
@@ -8723,13 +8728,26 @@ var ObservabilityRegistry = class {
|
|
|
8723
8728
|
//#endregion
|
|
8724
8729
|
//#region src/span_processors/sensitive-data-filter.ts
|
|
8725
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
|
+
/**
|
|
8726
8742
|
* SensitiveDataFilter
|
|
8727
8743
|
*
|
|
8728
8744
|
* An SpanOutputProcessor that redacts sensitive information from span fields.
|
|
8729
8745
|
*
|
|
8730
8746
|
* - Sensitive keys are matched case-insensitively, normalized to remove separators.
|
|
8731
|
-
* - Sensitive values are redacted using
|
|
8747
|
+
* - Sensitive values are redacted using full, partial, or indexed redaction.
|
|
8732
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.
|
|
8733
8751
|
* - JSON strings containing sensitive fields are parsed and redacted.
|
|
8734
8752
|
* - If filtering a field fails, the field is replaced with:
|
|
8735
8753
|
* `{ error: { processor: "sensitive-data-filter" } }`
|
|
@@ -8739,6 +8757,7 @@ var SensitiveDataFilter = class {
|
|
|
8739
8757
|
sensitiveFields;
|
|
8740
8758
|
redactionToken;
|
|
8741
8759
|
redactionStyle;
|
|
8760
|
+
traceStates = /* @__PURE__ */ new Map();
|
|
8742
8761
|
constructor(options = {}) {
|
|
8743
8762
|
this.sensitiveFields = (options.sensitiveFields || [
|
|
8744
8763
|
"password",
|
|
@@ -8768,42 +8787,63 @@ var SensitiveDataFilter = class {
|
|
|
8768
8787
|
* @returns A new span with sensitive values redacted
|
|
8769
8788
|
*/
|
|
8770
8789
|
process(span) {
|
|
8771
|
-
|
|
8772
|
-
span.
|
|
8773
|
-
span.
|
|
8774
|
-
span.
|
|
8775
|
-
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);
|
|
8776
8796
|
return span;
|
|
8777
8797
|
}
|
|
8778
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
|
+
/**
|
|
8779
8819
|
* Recursively filter objects/arrays for sensitive keys.
|
|
8780
8820
|
* Handles circular references by replacing with a marker.
|
|
8781
8821
|
* Also attempts to parse and redact JSON strings.
|
|
8782
8822
|
*/
|
|
8783
|
-
deepFilter(obj, seen = /* @__PURE__ */ new WeakSet()) {
|
|
8823
|
+
deepFilter(obj, seen = /* @__PURE__ */ new WeakSet(), indexedState) {
|
|
8784
8824
|
if (obj === null || typeof obj !== "object") {
|
|
8785
8825
|
if (typeof obj === "string") {
|
|
8786
8826
|
const trimmed = obj.trim();
|
|
8787
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return this.redactJsonString(obj);
|
|
8827
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return this.redactJsonString(obj, indexedState);
|
|
8788
8828
|
}
|
|
8789
8829
|
return obj;
|
|
8790
8830
|
}
|
|
8791
8831
|
if (seen.has(obj)) return "[Circular Reference]";
|
|
8792
8832
|
seen.add(obj);
|
|
8793
8833
|
if (obj instanceof Date) return obj;
|
|
8794
|
-
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));
|
|
8795
8835
|
const filtered = {};
|
|
8796
8836
|
for (const key of Object.keys(obj)) {
|
|
8797
8837
|
const normKey = this.normalizeKey(key);
|
|
8798
|
-
if (this.isSensitive(normKey)) if (obj[key] && typeof obj[key] === "object") filtered[key] = this.deepFilter(obj[key], seen);
|
|
8799
|
-
else filtered[key] = this.redactValue(obj[key]);
|
|
8800
|
-
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);
|
|
8801
8841
|
}
|
|
8802
8842
|
return filtered;
|
|
8803
8843
|
}
|
|
8804
|
-
tryFilter(value) {
|
|
8844
|
+
tryFilter(value, indexedState) {
|
|
8805
8845
|
try {
|
|
8806
|
-
return this.deepFilter(value);
|
|
8846
|
+
return this.deepFilter(value, /* @__PURE__ */ new WeakSet(), indexedState);
|
|
8807
8847
|
} catch {
|
|
8808
8848
|
return { error: { processor: this.name } };
|
|
8809
8849
|
}
|
|
@@ -8833,11 +8873,11 @@ var SensitiveDataFilter = class {
|
|
|
8833
8873
|
* Attempt to parse a string as JSON and redact sensitive fields within it.
|
|
8834
8874
|
* If parsing fails or no sensitive data is found, returns the original string.
|
|
8835
8875
|
*/
|
|
8836
|
-
redactJsonString(str) {
|
|
8876
|
+
redactJsonString(str, indexedState) {
|
|
8837
8877
|
try {
|
|
8838
8878
|
const parsed = JSON.parse(str);
|
|
8839
8879
|
if (parsed && typeof parsed === "object") {
|
|
8840
|
-
const filtered = this.deepFilter(parsed, /* @__PURE__ */ new WeakSet());
|
|
8880
|
+
const filtered = this.deepFilter(parsed, /* @__PURE__ */ new WeakSet(), indexedState);
|
|
8841
8881
|
return JSON.stringify(filtered);
|
|
8842
8882
|
}
|
|
8843
8883
|
return str;
|
|
@@ -8849,17 +8889,36 @@ var SensitiveDataFilter = class {
|
|
|
8849
8889
|
* Redact a sensitive value.
|
|
8850
8890
|
* - Full style: replaces with a fixed token.
|
|
8851
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.
|
|
8852
8895
|
*
|
|
8853
|
-
* Non-string values are converted to strings before partial redaction.
|
|
8854
|
-
*/
|
|
8855
|
-
redactValue(value) {
|
|
8856
|
-
if (this.redactionStyle === "
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
|
|
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();
|
|
8861
8921
|
}
|
|
8862
|
-
async shutdown() {}
|
|
8863
8922
|
};
|
|
8864
8923
|
//#endregion
|
|
8865
8924
|
//#region src/default.ts
|