Auto-Redaction
Wide events capture comprehensive context, which makes it easy to accidentally log sensitive data. Auto-redaction scrubs PII from events before console output and before any drain sees the data.
Redaction is enabled by default in production (NODE_ENV === 'production'). In development, it is off so you see full values for debugging. No configuration needed — just deploy.
Opting Out
If you need to disable redaction in production:
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
redact: false,
},
})
import { createEvlog } from 'evlog/next'
export const { withEvlog, useLogger } = createEvlog({
service: 'my-app',
redact: false,
})
import { initLogger } from 'evlog'
initLogger({
env: { service: 'my-app' },
redact: false,
})
You can also enable redaction explicitly in development with redact: true.
Smart Masking
Built-in patterns use partial masking instead of flat [REDACTED] — preserving enough context for debugging while protecting the actual data.
| Pattern | Example Input | Masked Output |
|---|---|---|
creditCard | 4111111111111111 | ****1111 |
email | alice@example.com | a***@***.com |
ipv4 | 192.168.1.100 | ***.***.***.100 |
phone | +33 6 12 34 56 78 | +33 ****5678 |
jwt | eyJhbGciOiJIUzI1NiIs... | eyJ***.*** |
bearer | Bearer sk_live_abc123... | Bearer *** |
iban | FR76 3000 6000 0112 ...189 | FR76****189 |
127.0.0.1 and 0.0.0.0 are excluded from IPv4 masking since they are not real client addresses.Configuration
Key-Based Redaction
Redact fields by key name at any nesting depth — no need to know the full dot-notation path. A single password entry covers user.password, data.a.b.password, and every other occurrence:
evlog: {
redact: {
keys: ['password', 'apiKey', 'authorization', 'cookie'],
keyPatterns: [/.*_token$/i],
}
}
Key-based redaction replaces the entire value (including nested objects) with replacement. Use keyPatterns for regex on property names; use patterns when you need regex on string values.
This matches the semantics of auditDiff({ redactPaths: ['password'] }) — the same key-name rules, but applied globally at emit time.
Custom Paths
Add exact dot-notation paths when you need to target one location only (e.g. hyphenated header keys):
evlog: {
redact: {
paths: ['headers.x-forwarded-for'],
keys: ['authorization'],
}
}
Path-based redaction replaces the entire leaf value with the replacement string (default [REDACTED]), regardless of content. Unlike keys, paths: ['password'] only redacts a top-level password field.
Selective Built-ins
Pick only the patterns you need:
evlog: {
redact: {
builtins: ['email', 'creditCard'],
}
}
Custom Patterns
Add your own regex patterns. These use the flat replacement string, not smart masking:
evlog: {
redact: {
patterns: [/SECRET_\w+/g, /sk_live_\w+/g],
replacement: '***',
}
}
Disable Built-ins
If you only want custom redaction:
evlog: {
redact: {
builtins: false,
paths: ['user.ssn'],
patterns: [/INTERNAL_\w+/g],
}
}
Configuration Reference
| Option | Type | Default | Description |
|---|---|---|---|
redact | boolean | RedactConfig | true in production | Enabled by default in production. false to disable. Object for fine-grained control |
keys | string[] | undefined | Object key names to redact at any depth (e.g. password → all password fields) |
keyPatterns | RegExp[] | undefined | Regex on object key names at any depth (e.g. /.*_token$/) |
paths | string[] | undefined | Exact dot-notation paths only (e.g. headers.x-forwarded-for) |
patterns | RegExp[] | undefined | Custom regex on string values. Uses flat replacement string |
builtins | false | string[] | All enabled | false disables built-ins. Array selects specific ones |
replacement | string | '[REDACTED]' | Replacement for keys, paths, and custom patterns. Built-ins use smart masking instead |
Available built-in names: creditCard, email, ipv4, phone, jwt, bearer, iban.
How It Works
Redaction runs inside the emit pipeline, after the wide event is fully built but before any output:
- Key redaction — matching key names at any depth replaced with
[REDACTED] - Path redaction — exact dot-notation leaves replaced with
[REDACTED] - Smart masking — built-in patterns scan all string values recursively with partial masking
- Pattern redaction — custom regex patterns scan all string values with flat replacement
- Console output — masked event printed to stdout
- Drain — masked event sent to external services
Production Example
Redaction is already on by default in production. Combine with sampling for a typical setup:
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
env: { service: 'my-app' },
},
$production: {
evlog: {
sampling: {
rates: { info: 10, debug: 0 },
keep: [{ status: 400 }, { duration: 1000 }],
},
},
},
})
import { createEvlog } from 'evlog/next'
export const { withEvlog, useLogger } = createEvlog({
service: 'my-app',
sampling: {
rates: { info: 10, debug: 0 },
keep: [{ status: 400 }, { duration: 1000 }],
},
})
import { initLogger } from 'evlog'
initLogger({
env: { service: 'my-app' },
sampling: {
rates: { info: 10, debug: 0 },
keep: [{ status: 400 }, { duration: 1000 }],
},
})
Before / After
Without redaction, sensitive data lands in your logs and drains:
{
"user": { "email": "alice@example.com", "ip": "192.168.1.42" },
"payment": { "card": "4111111111111111" },
"auth": "Bearer sk_live_abc123def456"
}
With redact: true:
{
"user": { "email": "a***@***.com", "ip": "***.***.***.42" },
"payment": { "card": "****1111" },
"auth": "Bearer ***"
}
Same debugging context, no PII in your Axiom/Datadog/Sentry.
Next Steps
- Best Practices - Security guidelines and production checklist
- Sampling - Control log volume in production
- Configuration - Full configuration reference
Sampling
Control log volume with two-tier sampling. Head sampling drops noise by level, tail sampling rescues critical events based on outcome. Never miss errors, slow requests, or critical paths.
Typed Fields
Add compile-time type safety to your wide events with TypeScript module augmentation. Prevent typos and ensure consistent field names across your codebase.