Redact PII in Application Logs
Add structured log redaction for emails, tokens, and card numbers so sensitive data never lands in log storage.
Task
Introduce a redaction layer that scrubs PII and secrets before logs are written.
When to use this
A support request exposes logs containing full email addresses and tokens, turning debugging data into a compliance problem.
Prerequisites
- A structured logger with hooks
- A list of sensitive field names
- Log retention that includes central storage
Steps
1. Redact known sensitive keys recursively.
const SENSITIVE = /^(password|token|authorization|email|cardNumber|ssn)$/i;
function redact(obj) {
if (Array.isArray(obj)) return obj.map(redact);
if (obj && typeof obj === 'object') {
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, SENSITIVE.test(k) ? '[REDACTED]' : redact(v)]));
}
return obj;
}Expected output: Sensitive keys are replaced with a marker before serialisation.
2. Add a test asserting secrets never appear.
test('no secrets in logs', () => {
const line = JSON.stringify(redact({ user: 'a', token: 'super-secret' }));
expect(line).not.toContain('super-secret');
});Expected output: A regression test fails if a sensitive field is ever logged verbatim.
Troubleshooting
Problem
Secrets inside free-text messages
Solution
Avoid interpolating raw values into messages; log identifiers instead.
Problem
Redaction too narrow
Solution
Maintain the sensitive key list and test patterns against real log samples.
Limitations
- Cannot catch PII embedded in unstructured text.
- Over-redaction can hurt debugging.
- Requires the logger to route through redaction.
What this recipe does
Introduce a redaction layer that scrubs PII and secrets before logs are written.
A support request exposes logs containing full email addresses and tokens, turning debugging data into a compliance problem.
Prerequisites
- A structured logger with hooks
- A list of sensitive field names
- Log retention that includes central storage
Procedure
- Redact known sensitive keys recursively.
const SENSITIVE = /^(password|token|authorization|email|cardNumber|ssn)$/i;
function redact(obj) {
if (Array.isArray(obj)) return obj.map(redact);
if (obj && typeof obj === 'object') {
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, SENSITIVE.test(k) ? '[REDACTED]' : redact(v)]));
}
return obj;
}
Expected result: Sensitive keys are replaced with a marker before serialisation.
- Add a test asserting secrets never appear.
test('no secrets in logs', () => {
const line = JSON.stringify(redact({ user: 'a', token: 'super-secret' }));
expect(line).not.toContain('super-secret');
});
Expected result: A regression test fails if a sensitive field is ever logged verbatim.
How to confirm it worked
Run the commands above and confirm every expected result in order. If a step does not produce the stated output, use the troubleshooting notes before continuing. A passing run shows a test that logs a secret fails the redaction assert, and real logs contain markers instead of raw values.
Troubleshooting
Secrets inside free-text messages — Avoid interpolating raw values into messages; log identifiers instead.
Redaction too narrow — Maintain the sensitive key list and test patterns against real log samples.
Limitations
- Cannot catch PII embedded in unstructured text.
- Over-redaction can hurt debugging.
- Requires the logger to route through redaction.