Skip to main content
Redaction runs after the capture policy and before events hit the network. It walks every event and strips anything matching your rules, replacing the value with "[REDACTED]". Three layers, applied in order:
createBoundaryLogger({
  redact: {
    fields:   ["ssn", "email"],                     // 1. exact key names
    patterns: [/\b\d{3}-\d{2}-\d{4}\b/],            // 2. regex over string leaves
    custom:   (value, path) => scrub(value, path),  // 3. last-chance callback
  },
});
Each layer is optional. They compose — you can use any combination.

Layer 1 — fields

Matches by object key (case-sensitive, deep). Whenever the walker encounters a key in this list, the entire value at that key is replaced with "[REDACTED]" — even if it’s an object, array, or deeply nested.
redact: {
  fields: ["ssn", "apiKey", "password", "phone"],
}
Matches { user: { ssn: "123-45-6789" } } and { customers: [{ phone: "..." }] } alike.

Layer 2 — patterns

Regex patterns applied to every string leaf in the event. Each match is replaced with "[REDACTED]" — the rest of the string is preserved.
redact: {
  patterns: [
    /\b\d{3}-\d{2}-\d{4}\b/,                    // SSN
    /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/,  // credit card
    /[\w.+-]+@[\w-]+\.[\w.-]+/,                 // email
    /\+?\d{1,3}[\s-]?\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{4}/,  // phone
    /sk-[A-Za-z0-9]{20,}/,                      // OpenAI-style secret
  ],
}
Patterns run across all nested string values — including inside repairs[].content and any raw input / output you’ve opted into capturing.

Layer 3 — custom

Runs last, after fields and patterns. Called once for every leaf in the event tree, with the current value and the full JSON path to that leaf.
redact: {
  custom: (value, path) => {
    // Hash customer IDs but keep them correlatable
    if (path.includes("customerId") && typeof value === "string") {
      return hash(value);
    }
    // Drop a field entirely by returning undefined
    if (path.includes("internalToken")) return undefined;
    // Default — keep the value as-is
    return value;
  },
}
Return:
  • the value unchanged to keep it,
  • a transformed value to replace it,
  • undefined to drop the leaf.

Path semantics

path is an array of string segments. For { repairs: [{ role: "user", content: "..." }] }, the walker calls custom with:
  • path = ["repairs", "0", "role"]
  • path = ["repairs", "0", "content"]
Array indices are stringified. Use path.includes(...) for coarse matching or path.join(".") for precise matching like repairs.0.content.

Cycle safety

The redactor tolerates cyclic object references. Each object is entered at most once; on revisit, the walker returns "[CYCLE]" in place. You never crash, even if beforeSend enrichment accidentally creates a cycle.

Composability example

Scrub emails/SSNs by pattern, wipe specific fields by name, hash the customer ID via custom:
createBoundaryLogger({
  redact: {
    fields: ["ssn", "passport", "dob"],
    patterns: [
      /\b\d{3}-\d{2}-\d{4}\b/,
      /[\w.+-]+@[\w-]+\.[\w.-]+/,
    ],
    custom: (value, path) => {
      if (path.at(-1) === "customerId" && typeof value === "string") {
        return `cust_${hash(value).slice(0, 10)}`;
      }
      return value;
    },
  },
});

When redaction isn’t enough

If you need to make a decision about the whole event (e.g. drop all events for a given contract, stamp a trace ID across every event), use beforeSend. Redaction works per leaf; beforeSend works per event.

See also

Capture policy

Strip buckets of data before redaction runs

beforeSend

Last-chance transform or drop