Пакет · logger

Маскирование чувствительных данных

Секреты легко попадают в логи — тело запроса с password, заголовок с токеном authorization. SensitiveMaskingProcessor скрывает их до того, как они достигнут любого обработчика, так что они никогда не попадают на диск или в ваш агрегатор.

Добавьте процессор к каналу

Процессор опционален — добавляйте его после сборки менеджера. Навесьте его на нижележащий экземпляр Monolog у канала:

php
use Flytachi\Winter\Logger\Processor\SensitiveMaskingProcessor;

$manager->channel('http')->monolog()->pushProcessor(new SensitiveMaskingProcessor());

Теперь любой совпавший ключ заменяется на *** и в context, и в extra:

php
LoggerFactory::getLogger(self::class)->info('login attempt', [
  'username' => 'alice',
  'password' => 'hunter2',      // ← замаскировано
  'metadata' => [
      'token' => 'secret-jwt',  // ← вложенное, тоже замаскировано
  ],
]);
text
[2024-01-01 12:00:00] [INFO ] -http- [4821] (AuthService): login attempt {"username":"alice","password":"***","metadata":{"token":"***"}}

Что оно сопоставляет

  • Регистронезависимо по ключам — Password, PASSWORD и password совпадают одинаково.
  • Рекурсивно — вложенные массивы обходятся на любую глубину.
  • Применяется к context вызова. Поля из хранилища контекста попадают в extra позже — см. следующий раздел.

Порядок процессоров: почему extra не маскируется

pushProcessor() кладёт процессор в начало очереди, а Monolog выполняет её сверху вниз. Канал уже несёт ContextInjectingProcessor (его ставит менеджер при сборке), поэтому добавленное после маскирование окажется перед ним и отработает раньше, чем поля контекста вообще появятся в записи:

text
0. SensitiveMaskingProcessor   ← выполняется первым: extra ещё пуст
1. ContextInjectingProcessor   ← вливает поля контекста уже после маскирования

Для context вызова это не важно — он на месте с самого начала. Но секрет, положенный в хранилище контекста ($storage->set('authorization', ...)), в лог уйдёт открытым.

Если такие поля тоже нужно маскировать, поменяйте порядок: снимите внедрение контекста, положите маскирование под него и верните обратно.

php
$monolog = $manager->channel('http')->monolog();

$contextProcessor = $monolog->popProcessor();          // ContextInjectingProcessor
$monolog->pushProcessor(new SensitiveMaskingProcessor());
$monolog->pushProcessor($contextProcessor);            // снова наверх — теперь он первый

Проще же не класть секреты в хранилище контекста вовсе: там место идентификаторам запроса и пользователя, а не заголовкам авторизации.

Ключи, маскируемые по умолчанию (все заменяются на ***):

text
password  passwd  secret  token  access_token  refresh_token
api_key  apikey  authorization  auth  cookie  set-cookie
credit_card  card_number  cvv  ssn  pin

Авторитетный список — в Справочнике формата логов.

Добавьте свои ключи

Передайте дополнительные ключи в конструктор — они объединяются со значениями по умолчанию:

php
$processor = new SensitiveMaskingProcessor(['patient_id', 'insurance_number']);

$manager->channel('http')->monolog()->pushProcessor($processor);

Примените ко всем каналам

Глобального переключателя нет — добавьте процессор к каждому каналу, который хотите маскировать. Небольшой цикл на бутстрапе покроет все:

php
foreach (['http', 'cli', 'sys'] as $name) {
  if ($manager->hasChannel($name)) {
      $manager->channel($name)->monolog()->pushProcessor(new SensitiveMaskingProcessor());
  }
}

Значения сопоставляются по ключу, а не по содержимому

Процессор скрывает по имени поля, а не по значению. Секрет, залогированный под ключом не из списка (например, data), или вставленный в строку сообщения, пойман не будет — держите секреты в осмысленно названных ключах контекста и никогда не помещайте их в текст сообщения.

Связанное