|
| 1 | +--- |
| 2 | +title: Auto-Redaction |
| 3 | +description: Automatically scrub PII from wide events before console output and drains. Built-in smart masking for credit cards, emails, IPs, phone numbers, JWTs, and more. |
| 4 | +navigation: |
| 5 | + icon: i-lucide-eye-off |
| 6 | +links: |
| 7 | + - label: Best Practices |
| 8 | + icon: i-lucide-shield-check |
| 9 | + to: /core-concepts/best-practices |
| 10 | + color: neutral |
| 11 | + variant: subtle |
| 12 | + - label: Configuration |
| 13 | + icon: i-lucide-settings |
| 14 | + to: /core-concepts/configuration |
| 15 | + color: neutral |
| 16 | + variant: subtle |
| 17 | +--- |
| 18 | + |
| 19 | +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. |
| 20 | + |
| 21 | +**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. |
| 22 | + |
| 23 | +## Opting Out |
| 24 | + |
| 25 | +If you need to disable redaction in production: |
| 26 | + |
| 27 | +::code-group |
| 28 | +```typescript [nuxt.config.ts] |
| 29 | +export default defineNuxtConfig({ |
| 30 | + modules: ['evlog/nuxt'], |
| 31 | + evlog: { |
| 32 | + redact: false, |
| 33 | + }, |
| 34 | +}) |
| 35 | +``` |
| 36 | +```typescript [lib/evlog.ts (Next.js)] |
| 37 | +import { createEvlog } from 'evlog/next' |
| 38 | + |
| 39 | +export const { withEvlog, useLogger } = createEvlog({ |
| 40 | + service: 'my-app', |
| 41 | + redact: false, |
| 42 | +}) |
| 43 | +``` |
| 44 | +```typescript [index.ts (Hono / Express / Fastify)] |
| 45 | +import { initLogger } from 'evlog' |
| 46 | + |
| 47 | +initLogger({ |
| 48 | + env: { service: 'my-app' }, |
| 49 | + redact: false, |
| 50 | +}) |
| 51 | +``` |
| 52 | +:: |
| 53 | + |
| 54 | +You can also enable redaction explicitly in development with `redact: true`. |
| 55 | + |
| 56 | +## Smart Masking |
| 57 | + |
| 58 | +Built-in patterns use **partial masking** instead of flat `[REDACTED]` — preserving enough context for debugging while protecting the actual data. |
| 59 | + |
| 60 | +| Pattern | Example Input | Masked Output | |
| 61 | +|---------|---------------|---------------| |
| 62 | +| `creditCard` | `4111111111111111` | `****1111` | |
| 63 | +| `email` | `alice@example.com` | `a***@***.com` | |
| 64 | +| `ipv4` | `192.168.1.100` | `***.***.***.100` | |
| 65 | +| `phone` | `+33 6 12 34 56 78` | `+33 ****5678` | |
| 66 | +| `jwt` | `eyJhbGciOiJIUzI1NiIs...` | `eyJ***.***` | |
| 67 | +| `bearer` | `Bearer sk_live_abc123...` | `Bearer ***` | |
| 68 | +| `iban` | `FR76 3000 6000 0112 ...189` | `FR76****189` | |
| 69 | + |
| 70 | +::callout{icon="i-lucide-info" color="info"} |
| 71 | +`127.0.0.1` and `0.0.0.0` are excluded from IPv4 masking since they are not real client addresses. |
| 72 | +:: |
| 73 | + |
| 74 | +## Configuration |
| 75 | + |
| 76 | +### Custom Paths |
| 77 | + |
| 78 | +Add dot-notation paths to redact specific fields with `[REDACTED]`, on top of the built-in patterns: |
| 79 | + |
| 80 | +```typescript |
| 81 | +evlog: { |
| 82 | + redact: { |
| 83 | + paths: ['user.password', 'headers.authorization'], |
| 84 | + } |
| 85 | +} |
| 86 | +``` |
| 87 | + |
| 88 | +Path-based redaction replaces the **entire value** with the `replacement` string (default `[REDACTED]`), regardless of content. |
| 89 | + |
| 90 | +### Selective Built-ins |
| 91 | + |
| 92 | +Pick only the patterns you need: |
| 93 | + |
| 94 | +```typescript |
| 95 | +evlog: { |
| 96 | + redact: { |
| 97 | + builtins: ['email', 'creditCard'], |
| 98 | + } |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +### Custom Patterns |
| 103 | + |
| 104 | +Add your own regex patterns. These use the flat `replacement` string, not smart masking: |
| 105 | + |
| 106 | +```typescript |
| 107 | +evlog: { |
| 108 | + redact: { |
| 109 | + patterns: [/SECRET_\w+/g, /sk_live_\w+/g], |
| 110 | + replacement: '***', |
| 111 | + } |
| 112 | +} |
| 113 | +``` |
| 114 | + |
| 115 | +### Disable Built-ins |
| 116 | + |
| 117 | +If you only want custom redaction: |
| 118 | + |
| 119 | +```typescript |
| 120 | +evlog: { |
| 121 | + redact: { |
| 122 | + builtins: false, |
| 123 | + paths: ['user.ssn'], |
| 124 | + patterns: [/INTERNAL_\w+/g], |
| 125 | + } |
| 126 | +} |
| 127 | +``` |
| 128 | + |
| 129 | +## Configuration Reference |
| 130 | + |
| 131 | +| Option | Type | Default | Description | |
| 132 | +|--------|------|---------|-------------| |
| 133 | +| `redact` | `boolean \| RedactConfig` | `true` in production | Enabled by default in production. `false` to disable. Object for fine-grained control | |
| 134 | +| `paths` | `string[]` | `undefined` | Dot-notation paths to redact entirely (e.g. `user.password`) | |
| 135 | +| `patterns` | `RegExp[]` | `undefined` | Custom regex patterns. Uses flat `replacement` string | |
| 136 | +| `builtins` | `false \| string[]` | All enabled | `false` disables built-ins. Array selects specific ones | |
| 137 | +| `replacement` | `string` | `'[REDACTED]'` | Replacement string for paths and custom patterns. Built-in patterns use smart masking instead | |
| 138 | + |
| 139 | +Available built-in names: `creditCard`, `email`, `ipv4`, `phone`, `jwt`, `bearer`, `iban`. |
| 140 | + |
| 141 | +## How It Works |
| 142 | + |
| 143 | +Redaction runs inside the emit pipeline, after the wide event is fully built but before any output: |
| 144 | + |
| 145 | +1. **Path redaction** — targeted fields replaced with `[REDACTED]` |
| 146 | +2. **Smart masking** — built-in patterns scan all string values recursively with partial masking |
| 147 | +3. **Pattern redaction** — custom regex patterns scan all string values with flat replacement |
| 148 | +4. **Console output** — masked event printed to stdout |
| 149 | +5. **Drain** — masked event sent to external services |
| 150 | + |
| 151 | +::callout{icon="i-lucide-zap" color="info"} |
| 152 | +Redaction runs **after** the HTTP response is sent, so it adds zero latency to your API responses. |
| 153 | +:: |
| 154 | + |
| 155 | +## Production Example |
| 156 | + |
| 157 | +Redaction is already on by default in production. Combine with sampling for a typical setup: |
| 158 | + |
| 159 | +::code-group |
| 160 | +```typescript [nuxt.config.ts] |
| 161 | +export default defineNuxtConfig({ |
| 162 | + modules: ['evlog/nuxt'], |
| 163 | + evlog: { |
| 164 | + env: { service: 'my-app' }, |
| 165 | + }, |
| 166 | + $production: { |
| 167 | + evlog: { |
| 168 | + sampling: { |
| 169 | + rates: { info: 10, debug: 0 }, |
| 170 | + keep: [{ status: 400 }, { duration: 1000 }], |
| 171 | + }, |
| 172 | + }, |
| 173 | + }, |
| 174 | +}) |
| 175 | +``` |
| 176 | +```typescript [lib/evlog.ts (Next.js)] |
| 177 | +import { createEvlog } from 'evlog/next' |
| 178 | + |
| 179 | +export const { withEvlog, useLogger } = createEvlog({ |
| 180 | + service: 'my-app', |
| 181 | + sampling: { |
| 182 | + rates: { info: 10, debug: 0 }, |
| 183 | + keep: [{ status: 400 }, { duration: 1000 }], |
| 184 | + }, |
| 185 | +}) |
| 186 | +``` |
| 187 | +```typescript [index.ts (Hono / Express / Fastify)] |
| 188 | +import { initLogger } from 'evlog' |
| 189 | + |
| 190 | +initLogger({ |
| 191 | + env: { service: 'my-app' }, |
| 192 | + sampling: { |
| 193 | + rates: { info: 10, debug: 0 }, |
| 194 | + keep: [{ status: 400 }, { duration: 1000 }], |
| 195 | + }, |
| 196 | +}) |
| 197 | +``` |
| 198 | +:: |
| 199 | + |
| 200 | +## Before / After |
| 201 | + |
| 202 | +Without redaction, sensitive data lands in your logs and drains: |
| 203 | + |
| 204 | +```json |
| 205 | +{ |
| 206 | + "user": { "email": "alice@example.com", "ip": "192.168.1.42" }, |
| 207 | + "payment": { "card": "4111111111111111" }, |
| 208 | + "auth": "Bearer sk_live_abc123def456" |
| 209 | +} |
| 210 | +``` |
| 211 | + |
| 212 | +With `redact: true`: |
| 213 | + |
| 214 | +```json |
| 215 | +{ |
| 216 | + "user": { "email": "a***@***.com", "ip": "***.***.***.42" }, |
| 217 | + "payment": { "card": "****1111" }, |
| 218 | + "auth": "Bearer ***" |
| 219 | +} |
| 220 | +``` |
| 221 | + |
| 222 | +Same debugging context, no PII in your Axiom/Datadog/Sentry. |
| 223 | + |
| 224 | +## Next Steps |
| 225 | + |
| 226 | +- [Best Practices](/core-concepts/best-practices) - Security guidelines and production checklist |
| 227 | +- [Sampling](/core-concepts/sampling) - Control log volume in production |
| 228 | +- [Configuration](/core-concepts/configuration) - Full configuration reference |
0 commit comments