HttpClient молчит в логах? Два фильтра и один FileLogger
Кирилл Новожилов
Автор
Содержание
Вы подключили FileLogger, сделали $http->get(...), открыли файл — пусто. Или в файле есть заголовки, но нет JSON тела, из‑за которого вы и пришли. Или лог внезапно содержит чужие запросы из модулей ядра, а не только ваш сервис.
Это не баг HttpClient. Так устроена связка именованного логгера main.HttpClient, битовой маски HttpDebug и PSR‑3 уровня у FileLogger. Ниже — как это реально работает в ядре (main 26.x) и как включить логирование так, чтобы оно помогало, а не жгло диски и не светило токены.
Сюжет: интеграция «вроде живая», а ответа нет
Типичный вечер:
$http = new \Bitrix\Main\Web\HttpClient(['socketTimeout' => 5]);
$body = $http->post('https://pay.example/api/charge', $payload);
if ($body === false) {
// getError() говорит что-то вроде "stream timeout" — и дальше гадание
}
Хочется увидеть wire: какой URL ушёл, какие заголовки, что в теле, сколько занял handshake. В голове сразу: «нужен FileLogger». И вот тут начинается развилка, о которой молчит половина гайдов.
Сюрприз 1. Логгер сам по себе ничего не пишет
Внутри запроса живёт не «голый» HttpClient, а Handler (Curl или Socket). Логгер он берёт так:
// main/lib/Web/Http/Handler.php
$logger = Diag\Logger::create('main.HttpClient', [$this, $this->request]);
$this->setLogger($logger ?? new Log\NullLogger());
return ($this->logger instanceof Log\NullLogger ? null : $this->logger);
Нет записи main.HttpClient в секции loggers → фабрика вернула null → стоит NullLogger → getLogger() отдаёт null → тишина. Не ошибка, не «логи сломались». Просто выключено по умолчанию.
.settings.php / .settings_extra.php. Хотите только один клиент в своём сервисе — можно повесить логгер точечно на экземпляр (об этом ниже).Сюрприз 2. Сигнатура конструктора не такая, как в старых примерах
В сети до сих пор встречается что-то вроде:
// Устарело / не совпадает с текущим ядром
'constructor' => function (\Bitrix\Main\Web\HttpClient $http, $method, $url) { ... }
В актуальном коде в closure приходят другие аргументы — ровно те, что передаёт Handler::getLogger():
$this— объект, реализующийDebugInterface(сам handler);$this->request— PSR‑7RequestInterface.
Рабочий минимальный конфиг:
// bitrix/.settings_extra.php (удобно для dev; на prod лучше не держать ALL)
return [
'loggers' => [
'value' => [
'main.HttpClient' => [
'constructor' => static function (
\Bitrix\Main\Web\Http\DebugInterface $debug,
\Psr\Http\Message\RequestInterface $request,
) {
// Без этой строки тела запросов вы, скорее всего, не увидите
$debug->setDebugLevel(\Bitrix\Main\Web\HttpDebug::ALL);
$dir = $_SERVER['DOCUMENT_ROOT'] . '/../logs';
if (!is_dir($dir)) {
mkdir($dir, 0750, true);
}
return new \Bitrix\Main\Diag\FileLogger(
$dir . '/http-client.log',
5 * 1024 * 1024, // ротация ~5 МБ → *.old
);
},
'level' => \Psr\Log\LogLevel::DEBUG,
],
],
'readonly' => true,
],
];
setDebugLevel вызывается на handler’е, который пришёл первым аргументом. Это не декорация — без неё вы останетесь на дефолтной маске.Сюрприз 3. Два независимых фильтра
Даже когда FileLogger создан, сообщение должно пройти два сита.
Фильтр A — битовая маска HttpDebug
// main/lib/Web/HttpDebug.php
public const REQUEST_HEADERS = 0b00000001;
public const REQUEST_BODY = 0b00000010;
public const RESPONSE_HEADERS = 0b00000100;
public const RESPONSE_BODY = 0b00001000;
public const CONNECT = 0b00010000;
public const DIAGNOSTICS = 0b00100000;
public const ALL = 0b00111111;
// По умолчанию:
public const DEFAULT = self::CONNECT | self::REQUEST_HEADERS | self::RESPONSE_HEADERS;
DEFAULT — это connect + заголовки запроса/ответа. Тел в дефолте нет.
Отсюда классика: «логгер настроил, Authorization вижу, JSON — нет». Нужен REQUEST_BODY / RESPONSE_BODY или сразу ALL.
Фильтр B — PSR‑3 уровень у логгера
Дамп трафика в Handler::log() всегда уходит так:
$logger->debug($logMessage, $context);
Ошибки сети (curl_error, socket fail) — через $logger->error(...).
Если в .settings.php поставить 'level' => LogLevel::ERROR, вы получите только аварии, а «красивый» wire‑дамп отфильтруется. Для отладки интеграции нужен именно DEBUG.
Сюрприз 4. Первая строка лога — это не HTTP, а backtrace
Перед первым куском трафика handler пишет шапку:
----------
2026-07-27 … - example.test
/local/modules/vendor.pay/lib/Service/ChargeService.php:84
...
В коде:
$headMessage = "\n{delimiter}\n{date} - {host}\n{trace}";
$headContext = ['trace' => Diag\Helper::getBackTrace(10, DEBUG_BACKTRACE_IGNORE_ARGS, 5)];
$logger->debug($headMessage, $headContext);
То есть лог отвечает не только на «что ушло в сеть», но и на кто вызвал HttpClient. На проекте, где одинаковый URL дергают три модуля, это ценнее самого тела ответа.
Дальше — маркеры в духе:
***CONNECT to …>>>REQUEST/ заголовки- тело (если маска позволяет)
<<<RESPONSE/ заголовки / тело***TIME connect=…, handshake=…, request=…, total=…при флагеDIAGNOSTICS
Последнее особенно полезно: можно отличить «долго коннектимся» от «долго отвечает API», не подключая внешний профайлер.
Два способа включить логи — и когда какой
Кейс A Глобально через main.HttpClient (для охоты)
Ловит все клиенты: ваш код, ядро, соседний модуль, агент. Удобно, когда непонятно, кто вообще ходит наружу. Страшно на проде: шум, объём, секреты.
Держите такой блок в .settings_extra.php на dev/stage и не копируйте его в production‑конфиг.
Кейс B Точечно на экземпляре (для одного сервиса)
use Bitrix\Main\Diag\FileLogger;
use Bitrix\Main\Web\HttpClient;
use Bitrix\Main\Web\HttpDebug;
use Psr\Log\LogLevel;
$logger = new FileLogger('/var/log/bitrix/pay-http.log', 2 * 1024 * 1024);
$logger->setLevel(LogLevel::DEBUG);
$http = new HttpClient([
'socketTimeout' => 5,
'streamTimeout' => 20,
'debugLevel' => HttpDebug::ALL, // можно и через setDebugLevel()
]);
$http->setLogger($logger);
$body = $http->post('https://pay.example/api/charge', $payload);
Как это стыкуется с ядром: в createHandler() логгер и маска копируются на handler только если у клиента уже установлен $this->logger. Иначе handler пойдёт в фабрику main.HttpClient.
setLoggerна экземпляре — локальный override, настройкиloggersдля этого клиента не нужны;- нет
setLogger— решает толькоmain.HttpClientиз конфига.
Сюрприз 5. Куда нельзя писать файл
Пример из документации вида DOCUMENT_ROOT . '/http.log' опасен ровно настолько, насколько звучит: положили рядом с index.php — и при неудачном nginx/access получили утечку Authorization, тел вебхуков, персональных данных.
Мини-чеклист:
- Каталог вне document root (
/var/log/bitrix,../logsотносительно сайта). - Права: пишет пользователь PHP‑FPM, читает только ops/разработчик.
- На время отладки —
ALL; после поимки бага — выключить или сузить маску доDEFAULT. - Ротация: второй аргумент
FileLogger(байты).0— без ротации, файл растёт пока не упрётесь в диск. - Помнить: один общий
http-client.logна нагруженном стенде быстро смешает запросы; для глубокой отладки иногда удобнее суффикс отspl_object_hash($request).
Короткий сценарий «починить за 5 минут»
- Добавить
main.HttpClientв.settings_extra.phpкак выше (HttpDebug::ALL+LogLevel::DEBUG). - Убедиться, что каталог логов существует и writable для php.
- Повторить проблемный запрос.
- В шапке лога найти backtrace — понять, тот ли это код.
- По
***TIMEпонять, сеть это или ответ API. - Увидеть тело/статус — починить интеграцию.
- Удалить или закомментировать логгер на prod. Оставленный
ALL— это не «мониторинг», это архив секретов.
Что забрать с собой
| Ожидание | Реальность в ядре |
|---|---|
| «Положил FileLogger — увижу HTTP» | Нужен ещё main.HttpClient или setLogger + маска |
| «Раз логгер есть, будет всё» | DEFAULT без тел; дамп идёт через debug() |
| «Конструктор получает HttpClient» | Сейчас: DebugInterface + RequestInterface |
| «В логе только мой сервис» | Глобальный логгер видит весь сайт |
| «Файл в www/ удобно» | Удобно и атакующему |
FileLogger здесь — не «ещё один способ file_put_contents», а приёмник для уже встроенного отладочного канала HttpClient. Настроили два фильтра правильно — интеграция перестаёт быть чёрным ящиком. Настроили неправильно — получаете либо пустой файл, либо свалку с токенами.
Комментарии (0)
Пожалуйста, войдите в аккаунт, чтобы оставить комментарий
Оставить комментарийЗагрузка...
Пока нет ни одного комментария. Будьте первым!
Похожие статьи