A regular expression stripping card numbers from log lines works until the format changes, and it is applied after the value has already been passed around.
// a processor, on the way out
class RedactingProcessor
{
private const REDACT = ['password', 'token', 'card_number', 'cvv',
'authorization', 'secret'];
public function __invoke(array $record): array
{
array_walk_recursive($record['context'], function (&$v, $k) {
if (in_array(strtolower((string) $k), self::REDACT, true)) {
$v = '[redacted]';
}
});
return $record;
}
}
Redacting by key rather than by value pattern is what makes it reliable, and it only works because the logs are structured — a formatted message has no keys to inspect. The list is a denylist and therefore incomplete by nature; an allowlist of loggable keys is stricter and more work, and is the right choice for anything handling payment data. Neither helps if the whole request body is logged as one string, which is the common accident.