Логирование в Laravel: рабочая настройка без боли в проде
Как настроить каналы логов, разделить окружения и подключить внешний сбор ошибок в Laravel 11, чтобы через месяц не искать причину падения в пустом storage/logs.

Приложение упало ночью, а в storage/logs/laravel.log — гигабайтный файл с перемешанными сообщениями от очередей, HTTP-запросов и планировщика. Найти нужную ошибку среди них невозможно, ротации нет, диск забит. Это стандартный итог конфигурации по умолчанию, если её не трогать. Разберём, как настроить логирование и сбор ошибок в Laravel 11 так, чтобы через месяц эксплуатации у вас были читаемые логи, разделение по каналам и уведомления о критических сбоях. Дальше — приёмы для тех, кто уже писал Log::info() и упёрся в вопросы разделения окружений и стоимости хранения.
Что даёт Laravel из коробки и где это ломается
Laravel строит логирование поверх Monolog. Конфигурация живёт в config/logging.php, канал по умолчанию задаётся переменной LOG_CHANNEL в .env. В свежей установке это stack, который проксирует в канал single — один файл laravel.log без ограничения размера.
Проблема не в самом Monolog, а в том, что дефолт рассчитан на локальную разработку. В проде он даёт три беды сразу: файл растёт без ротации, все уровни сообщений валятся в одно место, а критические ошибки никто не видит до жалобы пользователя. Поэтому первое решение по любому проекту — переопределить каналы под окружение.
Уровни логирования: не всё подряд в один поток
Monolog использует уровни из стандарта PSR-3: debug, info, notice, warning, error, critical, alert, emergency — по возрастанию тяжести. Параметр level у канала означает минимальный уровень, который канал примет. Канал с level = warning пропустит warning и всё, что тяжелее, но проигнорирует info и debug.
На этом строится разделение: подробные debug-сообщения нужны в разработке и мешают в проде, а error и выше должны уходить туда, где вы их точно заметите.
Базовая конфигурация каналов
Начните с трёх переменных в .env для прода:
LOG_CHANNEL=stack
LOG_STACK=daily
LOG_LEVEL=info
LOG_DAILY_DAYS=14
LOG_LEVEL читается стандартным каналом single и daily и отсекает всё легче info. LOG_DAILY_DAYS задаёт, сколько дней хранить файлы канала daily — по умолчанию Laravel хранит 14 дней и удаляет старые сам. Это переменные из штатного config/logging.php Laravel 11; если у вас более старая версия, часть из них могла называться иначе — сверьтесь со своим файлом конфигурации.
Канал daily вместо single решает вопрос ротации: вместо одного бесконечного файла появляются laravel-2024-06-01.log, laravel-2024-06-02.log и так далее, старые чистятся автоматически.
Отдельный канал под очереди и планировщик
Смешивать логи HTTP-запросов с логами обработчиков очередей — верный способ ничего в них не найти. Добавьте отдельный канал в массив channels файла config/logging.php:
// config/logging.php, секция channels (Laravel 11)
'queue' => [
'driver' => 'daily',
'path' => storage_path('logs/queue.log'),
'level' => env('LOG_LEVEL', 'info'),
'days' => 14,
'replace_placeholders' => true,
],
В обработчике задания пишите явно в этот канал:
use Illuminate\Support\Facades\Log;
// внутри job-класса
Log::channel('queue')->info('Обработка заказа', [
'order_id' => $this->order->id,
'attempt' => $this->attempts(),
]);
Второй аргумент — контекст. Не склеивайте данные в строку сообщения руками: передавайте массивом, тогда при переходе на структурированный формат (об этом ниже) вы получите поля, по которым можно фильтровать.
Логи без контекста — это дневник, который вы не сможете прочитать. «Ошибка при сохранении» бесполезно. «Ошибка при сохранении, order_id 4471, user_id 12, код БД 23505» — уже расследование за минуту, а не за час.
Структурированные логи: JSON вместо строк
Пока приложение маленькое, текстовый лог читается глазами. Как только логов становится много и вы подключаете внешний сборщик (Grafana Loki, Elasticsearch, Datadog), текстовый формат превращается в проблему: по нему сложно фильтровать и агрегировать. Решение — форматтер JsonFormatter из Monolog.
Подключается через ключ formatter у канала:
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'info'),
'handler' => Monolog\Handler\StreamHandler::class,
'formatter' => Monolog\Formatter\JsonFormatter::class,
'with' => [
'stream' => 'php://stderr',
],
],
Вывод в php://stderr — стандартный подход для контейнеров: пусть логи собирает не приложение, а платформа (Docker, Kubernetes). Приложение печатает JSON-строку на строку, всё остальное — забота инфраструктуры. Это снимает с вас вопрос ротации файлов вообще.
Добавить общий контекст ко всем записям
Чтобы к каждой записи прицеплялся, например, идентификатор запроса, используйте Log::withContext() в middleware. Он добавляет поля во все последующие вызовы логгера в рамках запроса:
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Log;
// в методе handle() вашего middleware
$requestId = (string) Str::uuid();
Log::withContext(['request_id' => $requestId]);
Теперь все логи одного запроса можно связать по request_id — это критично, когда параллельно летят сотни запросов и их сообщения перемешаны.
Уведомления о критических ошибках
Логи в файле или в Loki — это то, куда вы смотрите, когда уже знаете о проблеме. А узнать о ней нужно раньше. Для этого критические уровни отправляйте в отдельный канал уведомлений.
Laravel из коробки поддерживает канал slack через входящий webhook:
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => 'critical',
],
URL webhook держите только в .env и никогда не коммитьте:
LOG_SLACK_WEBHOOK_URL=YOUR_SLACK_WEBHOOK_URL
level = critical означает, что в Slack пойдут только critical, alert и emergency — иначе вы утоните в шуме от обычных warning и перестанете читать канал вовсе. Чтобы отправлять и туда, и в файл, соберите оба канала в стек:
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'slack'],
'ignore_exceptions' => false,
],
Сравнение каналов: что под какую задачу
| Канал / драйвер | Куда пишет | Ротация | Когда использовать |
|---|---|---|---|
| single | один файл | нет | только локальная разработка |
| daily | файл на каждый день | по дням, авточистка | прод на одном сервере без внешнего сбора |
| stderr (monolog + JSON) | стандартный поток ошибок | на стороне платформы | контейнеры, Docker, Kubernetes |
| slack | Slack-канал | — | оповещения о critical и выше |
| stack | несколько каналов сразу | зависит от вложенных | комбинация: файл плюс алерты |
Мониторинг: что смотреть, кроме логов
Логи отвечают на вопрос «что случилось». Мониторинг — «работает ли сейчас». Минимум для Laravel-приложения:
- Здоровье приложения. В Laravel 11 из коробки есть маршрут
/up— health-эндпоинт, который возвращает 200, если приложение поднялось. Настройте на него внешнюю проверку (UptimeRobot, healthcheck платформы). - Очереди. Смотрите глубину очереди и число проваленных заданий в таблице
failed_jobs. Растущая очередь — первый признак, что воркеры не справляются или упали. - Отслеживание исключений. Sentry или аналог: он группирует одинаковые ошибки, показывает частоту и стек. Пакет
sentry/sentry-laravelинтегрируется как отдельный лог-канал и через обработчик исключений — подключение описано в его документации. - Метрики БД и медленные запросы. Laravel Telescope хорош для локальной отладки, но в проде его держат выключенным или под жёстким доступом — он сам создаёт нагрузку и хранит чувствительные данные.
Не логируйте персональные данные
Пароли, токены, номера карт не должны попадать в логи ни при каких условиях. При логировании исключений с телом запроса Laravel по умолчанию скрывает поля из списка в обработчике исключений. Проверьте, что туда добавлены все чувствительные ключи вашего проекта, а не только password.
Типичные ошибки, которые видно не сразу
- LOG_LEVEL=debug в проде. Диск забивается за дни, а среди debug-шума тонут реальные ошибки. В проде минимум —
info, лучшеwarningдля файлового канала. - Права на storage/logs. Если веб-сервер и CLI (планировщик, воркеры) работают под разными пользователями, часть логов пишется, часть — нет, с ошибкой прав. Настройте единого владельца каталога.
- Slack как единственный канал. Если webhook недоступен, а
ignore_exceptionsвыключен, попытка залогировать может уронить запрос. Держите Slack в стеке рядом с файлом, а не вместо него.
Что настроить дальше
Базу вы собрали: разделённые каналы, ротация, JSON для внешнего сбора и алерты на critical. Следующие шаги в порядке приоритета: подключить отслеживание исключений (Sentry) и убедиться, что группировка ошибок работает; настроить внешнюю проверку /up; вынести сбор логов на платформу через stderr, если переезжаете в контейнеры. Ключевая метрика, за которой стоит следить с первого дня, — частота записей уровня error и выше в единицу времени: её рост обычно опережает жалобы пользователей на несколько часов.
Репетитор 1—11 класс и ВУЗ: Математика и физика методом наведения на мысль, помощь с ДЗ.
Спросить за 8 ₽Источники: Laravel 11 — Logging (официальная документация), Monolog — репозиторий и документация
Частые вопросы
У меня Laravel 9 или 10, а не 11 — конфигурация подойдёт?
Что делать, если логи не пишутся вообще, а ошибок в приложении нет?
JSON-формат обязателен или можно оставить текстовый?
Как не утонуть в уведомлениях из Slack-канала?
Telescope можно оставить включённым в проде?
Попадут ли пароли пользователей в логи при ошибке?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.