В модулях Битрикс до сих пор часто пишут AddMessage2Log, file_put_contents в /upload/ или просто echo в агенте. Это работает до первого продакшена: логи размазаны, нет уровней, нет ротации, в лог попадают пароли и PII. Нормальный контракт в D7 — Bitrix\Main\Diag\Logger (и наследники вроде FileLogger): один интерфейс, уровни PSR-3, контекст массивом, подключение через .settings.php или свой сервис.
Ниже — практический разбор: как завести файловый логгер, писать из модуля/агента/контроллера, чем он отличается от AddMessage2Log и от блока exception_handling, как не засветить секреты. Связка с остальным D7: ошибки операции отдаёте через Result, а детали пишете в Logger; подписки на сбои — через EventManager; пороги и feature-флаги логирования — в Option / Configuration.
Что такое Logger в D7
Bitrix\Main\Diag\Logger — абстрактный логгер с уровнями в духе PSR-3: emergency, alert, critical, error, warning, notice, info, debug. Сообщение — строка с плейсхолдерами {key}, второй аргумент — массив контекста. Конкретная реализация решает, куда писать: файл, syslog, внешний коллектор.
Из коробки чаще всего используют Bitrix\Main\Diag\FileLogger: пишет в файл, умеет ограничивать размер (ротация по maxSize). Для отладки ядра и необработанных исключений отдельно настраивается блок exception_handling в .settings.php — это не «логгер модуля», а обработчик фаталов/исключений ядра.
Быстрый старт: FileLogger в коде модуля
Минимальный рабочий пример без ServiceLocator — создать логгер рядом с точкой вызова (агент, CLI, одноразовый скрипт):
use Bitrix\Main\Diag\FileLogger;
use Bitrix\Main\IO\Directory;
$logDir = $_SERVER['DOCUMENT_ROOT'] . '/local/logs';
if (!Directory::isDirectoryExists($logDir)) {
Directory::createDirectory($logDir);
}
$logger = new FileLogger($logDir . '/mymodule.log', 5_000_000); // ~5 MB
$logger->setLevel(\Psr\Log\LogLevel::INFO);
$logger->info('Import started', [
'iblockId' => 12,
'userId' => (int)($GLOBALS['USER']->GetID() ?? 0),
]);
try {
// ... бизнес-логика
$logger->info('Import finished', ['rows' => 1540]);
} catch (\Throwable $e) {
$logger->error('Import failed: {message}', [
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
]);
throw $e;
}
Плейсхолдеры {message} подставляются из контекста. Не склеивайте строку через конкатенацию, если значение может содержать переводы строк или секреты — контекст проще фильтровать и маскировать в одном месте.
Регистрация через ServiceLocator
В нормальном модуле логгер лучше один раз зарегистрировать и доставать через ServiceLocator. Тогда агент, контроллер и событие пишут в один файл с одним уровнем.
// local/php_interface/init.php или include.php модуля
use Bitrix\Main\DI\ServiceLocator;
use Bitrix\Main\Diag\FileLogger;
use Bitrix\Main\IO\Directory;
use Psr\Log\LogLevel;
$locator = ServiceLocator::getInstance();
$locator->addInstanceLazy('mymodule.logger', static function () {
$dir = $_SERVER['DOCUMENT_ROOT'] . '/local/logs';
if (!Directory::isDirectoryExists($dir)) {
Directory::createDirectory($dir);
}
$logger = new FileLogger($dir . '/mymodule.log', 10_000_000);
$logger->setLevel(LogLevel::WARNING); // на проде не INFO/DEBUG
return $logger;
});
// использование
/** @var \Psr\Log\LoggerInterface $logger */
$logger = ServiceLocator::getInstance()->get('mymodule.logger');
$logger->warning('Retry queue overflow', ['queue' => 'orders', 'size' => 1200]);
Уровень на проде держите не ниже warning/error, иначе диск и I/O съедят импорты. Для стенда уровень можно читать из Option::get('mymodule', 'log_level', 'warning') и менять без деплоя.
exception_handling в .settings.php
Это другой контур: необработанные исключения и ошибки PHP, которые доходят до ядра. Типичная настройка на BitrixVM / своём сервере:
// /bitrix/.settings.php (фрагмент)
'exception_handling' => [
'value' => [
'debug' => false,
'handled_errors_types' => E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED,
'exception_errors_types' => E_ALL & ~E_NOTICE & ~E_STRICT & ~E_USER_NOTICE,
'ignore_silence' => false,
'assertion_throws_exception' => true,
'assertion_error_type' => E_USER_ERROR,
'log' => [
'settings' => [
'file' => '/var/log/bitrix/exception.log',
'log_size' => 10_000_000,
],
],
],
'readonly' => false,
],
На проде debug => false, иначе посетитель увидит стек. Файл лога должен быть вне web-root или закрыт nginx/apache. Не путайте этот лог с бизнес-логами модуля: exception.log — про падения, mymodule.log — про доменные события (импорт оборвался, webhook отклонён, квота исчерпана).
AddMessage2Log vs Logger
AddMessage2Log($message, $moduleId)— старый хелпер, пишет в файл из настроек (часто/bitrix/modules/error.logили путь из настроек главного модуля). Нет уровней, слабый контроль формата, легко получить кашу из разных модулей в одном файле.Logger/FileLogger— явный файл (или свой sink), уровни, контекст, удобно тестировать и подменять в DI.- Для одноразовой отладки на стенде
AddMessage2Logещё терпим. Для кода, который уходит в прод и в Marketplace — заводите свой Logger.
// плохо на проде
AddMessage2Log(print_r($_REQUEST, true), 'mymodule');
// лучше
$logger->debug('Incoming request', [
'method' => $_SERVER['REQUEST_METHOD'] ?? '',
'path' => parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH),
// не логируйте целиком $_REQUEST / заголовки Authorization
]);
Связка с Result и агентами
Типичный паттерн сервиса: наружу — Result, внутрь — лог. Вызывающий код смотрит isSuccess(), а детали остаются в файле для разбора инцидента.
use Bitrix\Main\Result;
use Bitrix\Main\Error;
function runSync(\Psr\Log\LoggerInterface $logger): Result
{
$result = new Result();
$logger->info('Sync start');
try {
$count = doHeavySync();
$logger->info('Sync ok', ['count' => $count]);
$result->setData(['count' => $count]);
} catch (\Throwable $e) {
$logger->error('Sync failed: {message}', ['message' => $e->getMessage()]);
$result->addError(new Error('Синхронизация не удалась'));
}
return $result;
}
В агентах обязательно думайте про конкурентный запуск: лог «started» дважды подряд — симптом отсутствия flock/флага. Пишите в лог pid и исход блокировки, иначе потом не отличите нормальный ретрай от гонки.
Что нельзя писать в лог
- Пароли, токены OAuth, refresh-токены, webhook-секреты, ключи платежек.
- Полные тела карточек CRM с телефонами и паспортами — маскируйте или логируйте только ID.
- Сырой SQL с подставленными значениями, если там ПДн.
- Бинарные куски и огромные
print_rколлекций — режьте длину строки.
Простой хелпер маскирования перед записью:
function maskSecret(?string $value): string
{
if ($value === null || $value === '') {
return '';
}
$len = strlen($value);
if ($len <= 8) {
return str_repeat('*', $len);
}
return substr($value, 0, 4) . str_repeat('*', $len - 8) . substr($value, -4);
}
$logger->info('Token refreshed', [
'access' => maskSecret($token->getAccessToken()),
]);
Ротация, права и nginx
Второй аргумент FileLogger — максимальный размер файла. При превышении Битрикс переименовывает/обрезает по своей логике реализации — не рассчитывайте на logrotate-семантику один в один. На сервере дополнительно:
- Каталог
/local/logs— права пользователя PHP-FPM, не world-readable. - В nginx запретите выдачу
/local/logs/(и вообще/local/кроме того, что реально нужно с веба). - Для долгоживущих проектов лучше слать логи во внешний коллектор (journald, Vector, ELK) — FileLogger остаётся локальным буфером/фолбэком.
Типичные ошибки
- Лог в
/upload/— файл могут скачать или зацепить бэкапом публички. Держите рядом с/local/или в/var/log/.... - DEBUG на проде — диск, нагрузка, утечка данных. Уровень через Option/Configuration.
- Один общий error.log на все модули — невозможно фильтровать. Имя файла = модуль или bounded context.
- Логировать только «успех» — при инциденте нужны warning/error с контекстом ID сущности.
- Глотать исключение после лога — либо пробросьте дальше, либо верните
Resultс Error, иначе вызывающий решит, что всё ок. - Путать exception_handling и бизнес-лог — падения ядра и «заказ не выгрузился» живут в разных файлах и с разным retention.
Чеклист перед выкладкой
- Есть один сервис
*.loggerв ServiceLocator (или явная фабрика). - На проде уровень ≥ warning, путь вне web-root или закрыт веб-сервером.
- В сообщениях нет секретов и сырых PII.
- Ошибки операций идут в Result; детали — в лог.
- Каталог логов создаётся кодом или деплоем, права проверены под пользователем FPM.
- Понятно, кто смотрит лог при алерте (см. также мониторинг и Telegram-алерты на сайте).
Вывод
Diag\Logger / FileLogger — базовый инструмент модульной разработки на Битрикс: уровни, контекст, предсказуемый файл. Не заменяйте им обработку ошибок для UI (для этого Result), не смешивайте с exception_handling ядра и не тащите отладочный AddMessage2Log($_REQUEST) на прод. Заведите сервис логгера, закройте каталог от веба, маскируйте секреты — и разбор инцидентов перестанет быть археологией по пяти разным путям.
