← К заметкам

Заметки

Как логировать 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 храните в закрытом каталоге. Журнал приёма не доказывает успешное выполнение операции и не защищает от повторного события.