Заметки
Как логировать REST-события без токенов и полного payload
Полный дамп $_POST, заголовков и тела запроса удобен до первого токена или письма клиента в журнале. Для фиксации факта приёма обычно достаточно имени события, ID объекта и времени на сервере.
Записывать только выбранные поля
Функция вызывается после проверки подлинности события вашим обработчиком. Она не заменяет авторизацию. Каталог /var/log/bitrix-events нужно заранее создать вне document root и разрешить запись только процессу обработчика.
<?php
function logRestEvent(array $verified_event, string $log_dir): void {
$event_name = $verified_event['event'] ?? '';
if (!is_string($event_name) || !preg_match('/\A[A-Z0-9_]{1,80}\z/', $event_name)) {
throw new InvalidArgumentException('Неизвестный формат имени события.');
}
$object_id = filter_var($verified_event['data']['FIELDS']['ID'] ?? null, FILTER_VALIDATE_INT);
$record = [
'received_at' => gmdate('c'),
'event' => $event_name,
'object_id' => $object_id !== false && $object_id > 0 ? $object_id : null,
];
$log_path = rtrim($log_dir, '/') . '/events-' . gmdate('Y-m-d') . '.log';
$line = json_encode($record, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) . PHP_EOL;
$old_mask = umask(0077);
try {
$handle = fopen($log_path, 'ab');
} finally {
umask($old_mask);
}
if ($handle === false) {
throw new RuntimeException('Журнал недоступен.');
}
try {
if (!flock($handle, LOCK_EX)) {
throw new RuntimeException('Не удалось заблокировать журнал.');
}
$file_stat = fstat($handle);
if ($file_stat === false || $file_stat['size'] + strlen($line) > 10 * 1024 * 1024) {
throw new RuntimeException('Превышен дневной размер журнала.');
}
if (fwrite($handle, $line) !== strlen($line)) {
throw new RuntimeException('Запись журнала не завершена.');
}
} finally {
fclose($handle);
}
}Что нужно настроить вокруг журнала
Пример требует PHP 7.3+. Он создаёт отдельный файл на день и ограничивает его размер 10 МиБ. Старые файлы удаляйте по сроку хранения через logrotate или отдельное задание. Ошибка записи должна попадать в мониторинг обработчика, иначе достижение лимита останется незаметным.
В журнал не попадают auth, заголовки, адрес webhook и содержимое CRM-полей. Даже внутренние ID храните в закрытом каталоге. Журнал приёма не доказывает успешное выполнение операции и не защищает от повторного события.