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
Path Patterns
Use a single paths array with dot-notation and globs. A bare segment like password is shorthand for **.password — it redacts that key at any nesting depth:
evlog: {
redact: {
paths: [
'password', // same as '**.password'
'*_token', // key-name glob at any depth
'headers.x-forwarded-for', // exact path
'user.*', // everything directly under user
],
}
}
| Pattern | Matches |
|---|---|
user.email | Exact path only |
password or **.password | password key at any depth |
*_token | Key names like access_token, refresh_token |
user.* | user.email, user.password, etc. |
audit.changes.*.password | Mixed exact + wildcard segments |
Path redaction replaces the entire value (including nested objects) with replacement. Use patterns when you need regex on string values inside fields.
This matches auditDiff({ redactPaths: ['password'] }) — same glob syntax, applied globally at emit time.
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 |
paths | string[] | undefined | Dot-notation paths with globs (password, **.password, *_token, user.*) |
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 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:
- Path redaction — exact paths and globs 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.