Data sanitization
Built-in redaction patterns, presets, custom rules and the audit trail.
What happens by default
Before a log is queued, the SDK copies it and cleans the copy. It looks at the message, the data you attach, the context, and the metadata. Two things happen:
- text matching a known pattern, such as an email address, is replaced;
- fields whose names suggest secrets, such as
password, are masked.
The error’s name, message and stack, the page address, the referrer and the user agent are not cleaned. See Privacy and PII redaction for the full picture.
Built-in patterns
| Pattern | Replaced with | Notes |
|---|---|---|
| Email address | [EMAIL_REDACTED] | |
| US Social Security number | [SSN_REDACTED] | Any nine digits, with or without dashes |
| Card number | [CARD_REDACTED] | 16 digits, optionally in groups of four |
| US phone number | [PHONE_REDACTED] | Ten digits, optionally with +1 and separators |
| IPv4 address | [IP_REDACTED] | Version numbers like Chrome/137.0.0.0 are kept |
| API key or token | [API_KEY_REDACTED] | api_key, token, secret, password or pwd, then = or :, then 20 or more characters |
| JWT | [JWT_REDACTED] | Three dot-separated parts starting with eyJ |
| Bank account number | [ACCOUNT_REDACTED] | 8 to 17 digits after account, acct, routing, iban or bank |
| US driving licence | [DL_REDACTED] | A capital letter and 8 digits |
| Passport number | [PASSPORT_REDACTED] | One or two capital letters and 6 to 9 digits |
The patterns are exported as PII_PATTERNS. They only look at text: a number stored as a number is left alone, but the same digits inside a string can match. In a string, a nine-digit order ID becomes [SSN_REDACTED] and a ten-digit Unix timestamp becomes [PHONE_REDACTED].
Field masking
Any field in the data, context or metadata whose name contains one of these words, ignoring case, has its string value masked: password, secret, token, key, ssn, email. The first and last characters are kept and the rest become asterisks; values of 4 characters or fewer become [ANONYMIZED].
logger.info('Signed in', { email: 'ada@example.com', apiKey: 'example-key-123', plan: 'pro' });
// Sent as: { email: '[**************]', apiKey: 'e*************3', plan: 'pro' }The email value was first replaced by the email pattern, then masked because of its field name. Matching is by substring, so key also masks fields such as keyboardLayout or monkey.
Presets
| Preset | Patterns | Field masking | Audit trail |
|---|---|---|---|
BALANCED | All ten | password, secret, token, key, ssn, email | On |
STRICT | All ten | The same, plus phone and address | On |
LENIENT | Only SSN, card, API key, JWT and bank account | Off | Off |
BALANCED is what you get without configuring anything. Pick another with SANITIZATION_PRESETS:
LENIENT sends passwords in plain text
LENIENT turns field masking off and drops the email pattern, so a password or email field in your data is sent as it is. Use it only where you are sure no such data is logged.import { Apperio, SANITIZATION_PRESETS } from 'apperio';
const logger = new Apperio({
apiKey: 'your-api-key',
projectId: 'your-project-id',
sanitization: { config: SANITIZATION_PRESETS.STRICT },
});Custom rules
Add a rule for data only your app has. Give the pattern the g flag, or only the first match in each string is replaced.
logger.addCustomSanitizationRule({
pattern: /CUST-\d{6}/g,
replacement: '[CUSTOMER_ID]',
description: 'Customer number',
severity: 'medium',
category: 'custom',
});
logger.removeCustomSanitizationRule('Customer number'); // removed by descriptionThe full configuration
sanitization.config takes a complete SanitizationConfig. The easiest way to change one part is to start from a preset:
import { Apperio, SANITIZATION_PRESETS } from 'apperio';
const logger = new Apperio({
apiKey: 'your-api-key',
projectId: 'your-project-id',
sanitization: {
config: {
...SANITIZATION_PRESETS.BALANCED,
sensitiveFields: [...SANITIZATION_PRESETS.BALANCED.sensitiveFields, 'address'],
customRules: [
{
pattern: /CUST-\d{6}/g,
replacement: '[CUSTOMER_ID]',
description: 'Customer number',
severity: 'medium',
category: 'custom',
},
],
},
},
});| Field | Type | What it does |
|---|---|---|
enabled | boolean | Whether this configuration runs at all. |
rules | SanitizationRule[] | The patterns to apply. PII_PATTERNS by default. |
customRules | SanitizationRule[] | Your own patterns, applied after rules. |
sensitiveFields | string[] | Field names to mask. |
anonymizationEnabled | boolean | Whether field masking runs. |
auditEnabled | boolean | Whether the audit trail records anything. |
retentionPolicy | RetentionPolicy | Required by the type, but it doesn’t affect stored data. Retention is set per project in Settings › Retention. |
preserveStructure | boolean | Required by the type; has no effect. |
Changing it at runtime
logger.updateSanitizationConfig({ sensitiveFields: ['password', 'secret', 'token'] });
const current = logger.getSanitizationConfig();Audit trail
With auditEnabled on, the SDK records each clean-up in memory: when it happened, sizes before and after, and which rules matched. It keeps the last 1,000 entries and never sends them anywhere. Read it while debugging your rules:
const entries = logger.getAuditTrail(); // [{ timestamp, operation, rulesApplied, ... }]
logger.clearAuditTrail();Turning it off
import { Apperio } from 'apperio';
const logger = new Apperio({
apiKey: 'your-api-key',
projectId: 'your-project-id',
sanitization: { enabled: false },
});Nothing is redacted on the server