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

PatternReplaced withNotes
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].

TypeScript
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

PresetPatternsField maskingAudit trail
BALANCEDAll tenpassword, secret, token, key, ssn, emailOn
STRICTAll tenThe same, plus phone and addressOn
LENIENTOnly SSN, card, API key, JWT and bank accountOffOff

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.
TypeScript
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.

TypeScript
logger.addCustomSanitizationRule({
  pattern: /CUST-\d{6}/g,
  replacement: '[CUSTOMER_ID]',
  description: 'Customer number',
  severity: 'medium',
  category: 'custom',
});

logger.removeCustomSanitizationRule('Customer number'); // removed by description

The full configuration

sanitization.config takes a complete SanitizationConfig. The easiest way to change one part is to start from a preset:

TypeScript
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',
        },
      ],
    },
  },
});
FieldTypeWhat it does
enabledbooleanWhether this configuration runs at all.
rulesSanitizationRule[]The patterns to apply. PII_PATTERNS by default.
customRulesSanitizationRule[]Your own patterns, applied after rules.
sensitiveFieldsstring[]Field names to mask.
anonymizationEnabledbooleanWhether field masking runs.
auditEnabledbooleanWhether the audit trail records anything.
retentionPolicyRetentionPolicyRequired by the type, but it doesn’t affect stored data. Retention is set per project in Settings › Retention.
preserveStructurebooleanRequired by the type; has no effect.

Changing it at runtime

TypeScript
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:

TypeScript
const entries = logger.getAuditTrail(); // [{ timestamp, operation, rulesApplied, ... }]
logger.clearAuditTrail();

Turning it off

TypeScript
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

Apperio’s servers store what the SDK sends. With sanitization off, everything you log is stored as it is.