Masking brick
Корисний лог майже завжди тягне за собою персональні дані: email у context, пароль у exception message, токен у SQL. Ховати їх «магією» в ядрі небезпечно (або сховає зайве, або недостатньо). Тому маскування — окрема capability: ви явно задаєте правила, а sinks самі вирішують, чи застосовувати їх.
addMasking() на
ConceptStack реєструє
extension-data-masker
як DataMaskerInterface і повертає
MaskingBuilder. Це ще не маскує
app.log: Logging / Database / Validation
підключають masker через свій withMasking().
Рецепт — Логування і маскування.
Методи MaskingBuilder:
| Метод | Параметри | Опис | Залежність |
|---|---|---|---|
setPatterns() |
array<string, string> $patterns |
Regex → заміна у рядках (message, trace, …) | — |
setKeyPatterns() |
list<string> $keyPatterns |
Regex на ім’я ключа; значення → *** |
— |
setRules() |
list<class-string<DataMaskerRuleInterface>> $rules |
Власні rule-класи з контейнера | — |
setPatterns()
$stack->addMasking()
->setPatterns([
'/[a-z0-9_\-\+\.]+@[a-z0-9\-]+\.[a-z]{2,}/i' => '***@***.***',
'/\d{4}-\d{4}-\d{4}-\d{4}/' => '****-****-****-****',
]);
Коли секрет уже всередині тексту — exception message, stack
trace, рядок SQL, чужий debug dump — key patterns безсилі:
ключа email немає. String patterns шукають
фрагмент у рядку і підставляють заміну. Потрібні саме тоді,
коли дані «розчинились» у message, а не лежать у структурованому
context.
setKeyPatterns()
$stack->addMasking()
->setKeyPatterns([
'/^email$/i',
'/.*password.*/i',
'/.*token.*/i',
'/.*secret.*/i',
]);
Для вашого коду це основний інструмент: ви самі кладете
['email' => $email] у
$logger->info(...). Якщо ім’я ключа (або
властивості об’єкта) матчить regex, ціле значення
стає *** — без здогадок про формат пароля.
Надійніше й простіше, ніж ловити email regex-ом у кожному
рядку, коли структура вже є.
setRules()
$stack->addMasking()
->setKeyPatterns(['/^email$/i'])
->setRules([CardLastFourRule::class]);
Regex не покриває все (часткове маскування картки, умовна
логіка по namespace). Тоді клас із
DataMaskerRuleInterface
(isSensitiveKey / apply) резолвиться
з контейнера. Деталі контракту й приклад —
Data Masker.
Підключення до sinks
Builder лише збирає правила. Щоб вони спрацювали на конкретному
каналі, sink має викликати withMasking()
(і тим самим require capability masking):
$stack->addMasking()
->setPatterns([ /* … */ ])
->setKeyPatterns([ /* … */ ]);
$stack->addLogging()
->toRotatingFile($rootPath . 'storage/logs/app.log')
->withMasking();
// пізніше, на діагностичних sinks:
// $stack->addValidation()->withLogging(...)->withMasking();
// $stack->addDatabase()->withQueryLogging(...)->withMasking();
Без withMasking() на Logging правила лежать у
контейнері, але app-логер їх не застосовує — можна навмисно
маскувати лише SQL, лишаючи app.log «сирим» у локальному
дебагу (рідко, але можливо). Типовий рецепт маскує app-канал
одразу з появою логера.
Що реєструє brick
-
DataMaskerInterface— shared masker зRegexDataMaskerRule(якщо є patterns / keyPatterns) і вашимиsetRules
Пакет не залежить від Monolog: Logging тягне masker опційно через factory. Прямий інжект у сервіси (audit, Slack) — теж валідний сценарій, див. extension page.
Рецепт —
8. Логування і маскування.
Пакет —
Data Masker.
Sink —
Logging brick
(withMasking()).