Логирование в Laravel: ошибки, из-за которых вы слепнете в проде
Логи на single-файле, забытый DEBUG, утечка паролей в трейсах и запросы к БД, которые никто не считает. Разбираем частые промахи в логировании и мониторинге Laravel и что настроить, чтобы видеть проблемы до звонка клиента.

Приложение отдаёт 500, клиент звонит, вы открываете storage/logs/laravel.log — а там файл на 8 ГБ, который grep читает минуту, и нужная запись потерялась среди тысяч однотипных. Или хуже: в трейсе исключения лежит пароль пользователя открытым текстом. Это не абстрактные страхи, а типичные последствия настроек логирования по умолчанию, которые в Laravel никто не трогает, пока не прижмёт.
Разбираем ошибки, которые встречаются в проде на Laravel 10.x и 11.x, и что с ними делать. Речь про то, что вы реально видите (или не видите) в логах и метриках. Устройство драйверов Monolog изнутри — отдельная тема, здесь только практика.
Ошибка 1. Драйвер single вместо daily
По умолчанию в config/logging.php дефолтный канал раньше был single — все записи льются в один файл laravel.log. Файл растёт без ограничений, никто его не ротирует, и через полгода у вас на диске гигабайты, которые невозможно быстро прочитать.
Канал daily режет логи по дням и хранит ограниченное число файлов. Проверьте переменную окружения:
# .env
LOG_CHANNEL=stack
LOG_STACK=daily
LOG_DAILY_DAYS=14Канал daily читает LOG_DAILY_DAYS (по умолчанию 14) и удаляет файлы старше этого срока. Без этого параметра даже daily копит файлы бесконечно — ротация по дате создания есть, а по количеству включается только через days.
Единственный лог-файл без ротации — это не логирование, а бомба замедленного действия под свободным местом на диске. Первое, что стоит проверить на новом проекте.
Почему не отдавать ротацию logrotate
Можно, если вы контролируете сервер и настроили logrotate сами. Но встроенный daily работает даже на shared-хостинге и в контейнере без cron, поэтому для большинства проектов это разумный дефолт. В Docker логичнее вообще писать в stderr — об этом ниже.
Ошибка 2. APP_DEBUG=true в проде
Самая опасная и самая частая. При APP_DEBUG=true Laravel показывает пользователю полный стек ошибки через Ignition — с фрагментами кода, значениями переменных, строками подключения к БД. Любой, кто вызовет исключение, увидит внутренности приложения.
- В проде всегда
APP_DEBUG=false. Проверяется одной строкой в.env. - Убедитесь, что
APP_ENV=production— часть пакетов ориентируется на это значение. - После деплоя выполните
php artisan config:cache, иначе изменения в.envне подхватятся при закешированном конфиге.
Отдельная ловушка: закешированный конфиг. Если вы поменяли APP_DEBUG в .env, но конфиг закеширован через config:cache, приложение продолжит читать старое значение. Всегда пересобирайте кеш конфига после правок окружения.
Ошибка 3. В логи утекают пароли и токены
Когда падает запрос с валидацией, Laravel по умолчанию не пишет тело запроса в лог. Но многие добавляют своё логирование вида Log::info('request', $request->all()) — и в файл улетают пароли, токены, номера карт.
Laravel исключает поля из логов только на уровне отображения ошибок (свойство $dontFlash в обработчике исключений — там обычно password и password_confirmation). При ручном логировании фильтровать надо самим:
use Illuminate\Support\Facades\Log;
// Laravel 11.x, ручное логирование запроса без чувствительных полей
$safePayload = collect($request->all())
->except(['password', 'password_confirmation', 'token', 'card_number'])
->toArray();
Log::info('Входящий запрос на оплату', $safePayload);Отдельно проверьте сторонние сервисы. Если вы отправляете исключения в Sentry или подобный сервис, включите там серверную фильтрацию (scrubbing) чувствительных данных — трейс исключения может содержать аргументы функций, а значит и пароли, переданные в метод.
Ошибка 4. Логи неотличимы друг от друга
Запись [2024-01-01 12:00:00] production.ERROR: Undefined index без контекста бесполезна. Вы не знаете, какой пользователь, какой запрос, какой ID заказа. Когда таких строк тысячи, найти нужную невозможно.
Добавляйте контекст
Второй аргумент методов Log — массив контекста, он попадает в запись:
Log::error('Не удалось списать средства', [
'user_id' => $user->id,
'order_id' => $order->id,
'amount' => $order->total,
'gateway_response' => $response->status(),
]);Чтобы не добавлять одни и те же поля вручную в каждый вызов, есть Log::withContext() — он прикрепляет контекст ко всем последующим записям в рамках запроса. Удобно ставить в middleware ID запроса, чтобы связать все логи одного HTTP-запроса.
Формат JSON для машинного разбора
Если логи уходят в систему сбора (ELK, Grafana Loki, Datadog), человекочитаемый формат мешает парсингу. Настройте JSON-форматтер на канале через Monolog\Formatter\JsonFormatter в конфиге канала — тогда каждая запись станет объектом с полями, по которым можно фильтровать.
Ошибка 5. Логирование в файл внутри контейнера
В Docker/Kubernetes писать в файл внутри контейнера — антипаттерн. Контейнер эфемерный, при перезапуске файл исчезает, а собирать логи из десятков контейнеров через файлы неудобно. Пишите в stderr, откуда их заберёт оркестратор.
# .env для контейнеризованного окружения
LOG_CHANNEL=stderrКанал stderr есть в дефолтном config/logging.php из коробки и использует StreamHandler на php://stderr. Дальше логи собирает драйвер Docker или агент в кластере.
Мониторинг: чего не видно в логах
Логи говорят, что уже сломалось. Мониторинг должен предупредить до того. Три вещи, которые в Laravel-проектах чаще всего не отслеживают:
- Медленные и N+1 запросы к БД. Одна страница может делать 300 запросов вместо трёх — в логах этого не видно, приложение просто медленное.
- Очереди. Задачи в очереди молча падают в
failed_jobs, и никто не смотрит туда, пока клиент не пожалуется на неотправленное письмо. - Время ответа и коды состояний. Рост доли 500 или медленных ответов — сигнал, который приходит раньше, чем поток ошибок в логах.
Ловим N+1 в разработке
Laravel умеет ронять исключение при ленивой загрузке связей — включается в AppServiceProvider:
use Illuminate\Database\Eloquent\Model;
public function boot(): void
{
// Только в локальной среде — в проде это положит приложение
Model::preventLazyLoading(! $this->app->isProduction());
}В проде для этого используйте Laravel Telescope или внешний APM. Telescope показывает все запросы, задачи очередей, письма и исключения — но у него есть цена.
Цена мониторинга: чем платите
| Инструмент | Что даёт | Чем платите |
|---|---|---|
| Telescope | Полная картина запросов, очередей, кеша | Пишет много данных в БД; в проде включать точечно и чистить |
| stderr + внешний сбор (Loki/ELK) | Централизованные логи, поиск | Инфраструктура для сбора и хранения |
| Внешний APM (Sentry, New Relic) | Ошибки, трейсы, метрики ответа | Подписка; передача данных вовне — проверьте scrubbing |
Telescope в проде без ограничений сам становится проблемой: его таблицы разрастаются. Настройте пробу (ignore_paths, семплирование) и регулярную очистку через telescope:prune.
Что настроить прямо сейчас
- Проверьте
APP_DEBUG=falseиAPP_ENV=productionв проде, пересоберите кеш конфига. - Переведите дефолтный канал на
dailyсLOG_DAILY_DAYSили наstderrв контейнере. - Пройдитесь по всем ручным
Log::и уберите из контекста пароли и токены. - Добавьте ID запроса в контекст через middleware, чтобы связывать логи одного запроса.
- Настройте мониторинг очередей и алерт на рост
failed_jobs. - Мерьте долю 5xx и время ответа — это первый сигнал деградации.
Дальше смотрите на объём логов: если один канал ERROR генерирует тысячи одинаковых записей в час, это не логи, а шум, который скрывает настоящие проблемы. Настройте разделение уровней по каналам и алерты только на то, что требует реакции.
Аналитик звонков: Разбор записи звонка по этапам сделки: где сорвалось, что упущено, три исправляемые рекомендации с цитатами.
Спросить за 90 ₽Источники: Laravel Logging — официальная документация, Laravel Telescope — официальная документация
Частые вопросы
Я на Laravel 9, а не на 11 — настройки те же?
Поменял APP_DEBUG в .env, но ошибки всё равно показываются полностью
Как понять, что в логи уже утекали пароли?
Telescope сильно замедляет прод?
Логи в stderr — а куда они денутся при перезапуске контейнера?
Не сработало: перевёл канал на daily, а файл laravel.log всё равно растёт
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.