Заметки

Мониторинг ошибок PHP на проде: как узнавать о падениях первым

M
Markabus
·11 сентября 2026 г.
Тёмный рабочий стол разработчика: на изогнутом мониторе дашборд мониторинга ошибок с синими графиками и красным всплеском, рядом второй экран с логами в терминале, клавиатура и телефон с уведомлением

На продакшене вывод ошибок в браузер выключен — и это правильно. Но у выключенного display_errors есть обратная сторона: пользователь видит белый экран, молча уходит, и никто об этом не узнаёт. Ошибка живёт в логе неделями, пока кто-нибудь случайно не откроет файл.

Мониторинг ошибок закрывает этот разрыв: приложение само сообщает о каждом исключении и фатальной ошибке во внешний сервис, тот группирует одинаковые ошибки, считает частоту и присылает уведомление. Разберём, как настроить это на PHP-проекте — от настроек интерпретатора до выбора между облаком и своим сервером.

Почему сырых логов на сервере не хватает

Логи — необходимый минимум, но как инструмент наблюдения за продом они слабы.

  • Их никто не читает. Логи смотрят, когда уже пришла жалоба. То есть после того, как проблему заметил клиент.
  • Нет группировки. Одна и та же ошибка, сработавшая 40 000 раз, — это 40 000 строк. Понять, что уникальных проблем всего три, по такому файлу тяжело.
  • Нет контекста. В строке лога есть сообщение, файл и номер строки — но не URL, параметры запроса, ID пользователя, версия релиза и стек вызовов с кодом вокруг каждого кадра.
  • Логи разъезжаются. На нескольких серверах, в контейнерах и под балансировщиком файл лежит в разных местах и умирает вместе с контейнером.

Трекер ошибок решает это одним приёмом: превращает поток событий в список проблем, отсортированный по важности. Про чтение самих логов на сервере есть отдельные заметки — шпаргалка по SSH-командам и разбор логов WordPress.

Шаг ноль: правильные настройки PHP на проде

Прежде чем подключать сторонний сервис, стоит привести в порядок сам интерпретатор. Минимальная боевая конфигурация в php.ini:

display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php/error.log
error_reporting = E_ALL

Два момента, которые часто делают неправильно. Первый: error_reporting на проде занижают до E_ALL & ~E_DEPRECATED & ~E_NOTICE, хотя логировать имеет смысл всё — deprecated-сообщения это ранний сигнал, что код сломается на следующей мажорной версии PHP. Второй: файл из error_log должен быть доступен на запись процессу PHP-FPM и ротироваться через logrotate, иначе однажды он съест диск.

Перехватить ошибки в самом коде можно через три точки входа: set_error_handler() для обычных ошибок, set_exception_handler() для непойманных исключений и register_shutdown_function() вместе с error_get_last() для фатальных. SDK трекеров внутри устроены именно так; писать это руками стоит, только если вы сознательно отказываетесь от готового решения.

Как устроен трекер ошибок

Терминология у всех инструментов примерно одинаковая, потому что де-факто стандартом стал протокол Sentry.

  • Event (событие) — один факт ошибки с контекстом: стек, запрос, окружение.
  • Issue (проблема) — группа одинаковых событий. Группировка идёт по «отпечатку» (fingerprint), который считается из типа исключения и верхних кадров стека. Именно поэтому 40 000 срабатываний превращаются в одну карточку со счётчиком.
  • Release (релиз) — версия кода, при которой случилась ошибка. Без неё нельзя ответить на главный вопрос разбора инцидента: «это было всегда или приехало с последним деплоем?»
  • Environment (окружение)production, staging, dev. Разделять обязательно, иначе шум со стейджа утопит боевые ошибки.
  • Breadcrumbs («хлебные крошки») — лента событий, предшествовавших ошибке: SQL-запросы, HTTP-вызовы, записи в лог. По ней восстанавливают, что именно делало приложение перед падением.
  • DSN — строка-адрес проекта, по которой SDK понимает, куда слать события. В PHP его держат в переменных окружения.

Подключаем Sentry к PHP-проекту

Sentry — самый распространённый вариант, а его SDK понимают и альтернативы из раздела ниже, так что подключение не запирает вас в одном сервисе.

Установка и инициализация

Актуальная ветка PHP-SDK — 4.x (на момент написания последняя версия 4.31.0, вышла 27 августа 2026 года). Требуется PHP 7.2+ и расширения curl, json, mbstring.

composer require sentry/sentry

Инициализацию ставят как можно раньше — в точке входа, до загрузки основного кода приложения:

\Sentry\init([
    'dsn'                => $_ENV['SENTRY_DSN'],
    'environment'        => $_ENV['APP_ENV'] ?? 'production',
    'release'            => $_ENV['APP_RELEASE'] ?? null,
    'sample_rate'        => 1.0,
    'traces_sample_rate' => 0.1,
    'send_default_pii'   => false,
]);

Дальше SDK сам перехватывает непойманные исключения и фатальные ошибки. Там, где исключение обрабатывается вручную, а знать о нём всё равно хочется, событие отправляют явно:

try {
    $this->chargePayment($order);
} catch (\Throwable $e) {
    \Sentry\captureException($e);
    $order->markPaymentFailed();
}

Об опциях: sample_rate (по умолчанию 1.0) — доля отправляемых ошибок, снижать её стоит только на очень нагруженном проекте, иначе теряются редкие, но важные падения. traces_sample_rate (по умолчанию null, трассировка выключена) отвечает за замеры производительности — это отдельная квота, и 0.1 обычно достаточно. environment по умолчанию production, но задавать его явно надёжнее.

Laravel и Symfony

Для фреймворков есть обёртки, которые сами подцепляются к обработчику исключений, логгеру и трассировке запросов: sentry/sentry-laravel (ветка 4.x, Laravel 6–13; Laravel 11+ на PHP 8.2+ — начиная с 4.3.0) и sentry/sentry-symfony. Ставятся тем же composer require, настройки живут в config/sentry.php и config/packages/sentry.yaml. Отдельно инициализировать базовый SDK не нужно.

WordPress и WooCommerce

На сайте без Composer подключение делают через mu-plugin: кладут SDK в wp-content/mu-plugins/ и вызывают \Sentry\init() оттуда — так код отработает раньше плагинов и темы и поймает их ошибки тоже. Полезно прокинуть в контекст ID пользователя, активную тему и список плагинов: на типичном сайте половина ошибок приходит из чужого кода. Штатный WP_DEBUG_LOG при этом не отключайте — задачи разные, подробности в заметке про логи WordPress.

Что не надо отправлять наружу

Трекер получает содержимое запросов, а значит легко утащит на чужой сервер то, чего там быть не должно. Опция send_default_pii по умолчанию выключена (false) — не включайте её, не подумав: с ней в события попадают IP-адрес, cookies и заголовки, включая Authorization. Всё, что нужно вычистить дополнительно, убирают в колбэке before_send:

'before_send' => function (\Sentry\Event $event): ?\Sentry\Event {
    $request = $event->getRequest();
    unset($request['cookies'], $request['headers']['authorization']);
    $event->setRequest($request);

    return $event;
},

Тот же колбэк работает как фильтр: если вернуть null, событие не уйдёт вообще. Так глушат мусор — ботов, падения на несуществующих URL, известные исключения (для последних есть и более прямой путь — опция ignore_exceptions со списком классов). И помните про закон о персональных данных: отправка ПДн в зарубежное облако — трансграничная передача со всеми вытекающими требованиями, и одно это часто решает вопрос в пользу self-hosted.

Сколько это стоит

У облачного Sentry бесплатный план Developer даёт 5 000 ошибок в месяц, 5 ГБ логов, 5 млн спанов и одного пользователя. Team — 26 $ в месяц при годовой оплате, 50 000 ошибок и неограниченное число пользователей. Business — 80 $ в месяц, та же квота по ошибкам, но снимаются ограничения по дашбордам и добавляются SAML/SCIM. Превышение квоты оплачивается отдельно по модели pay-as-you-go.

Ключевой нюанс: считаются события, а не уникальные проблемы. Зацикленный крон, роняющий исключение раз в секунду, выедает месячную квоту бесплатного плана примерно за полтора часа. Поэтому before_send и лимиты на стороне приложения — способ не получить внезапный счёт.

Своё или облако

Все три варианта ниже говорят на одном протоколе, то есть в коде меняется только DSN.

  • Self-hosted Sentry. Полная функциональность и никаких квот, но аппетиты серьёзные: официально требуется 4 ядра, 16 ГБ RAM плюс 16 ГБ swap (рекомендуется 32 ГБ) и 20 ГБ диска, Docker 19.03.6+ и Docker Compose 2.32.2+. Установка сильно упирается в дисковый ввод-вывод.
  • GlitchTip. Лёгкая реализация того же API: минимум 256 МБ RAM в all-in-one сборке (рекомендуется 512 МБ), PostgreSQL 14+, опционально Redis или Valkey 7+, около 30 ГБ диска на миллион событий в месяц. Умеет заметно меньше Sentry, но для задачи «ловить ошибки и присылать алерты» этого хватает.
  • Bugsink. Ещё один self-hosted трекер, совместимый с SDK Sentry, с прицелом на простую установку. Компромисс, если GlitchTip кажется аскетичным, а Sentry — тяжёлым.

Практическое правило: если проект один и трафик небольшой — берите бесплатный облачный план и не тратьте время. Если проектов много, данные чувствительные или квота бесплатного плана уже жмёт — GlitchTip на VPS за пару сотен рублей в месяц закрывает вопрос.

Алерты, которые не захочется выключить

Самый частый способ провалить мониторинг — уведомлять о каждом событии. Через неделю письма уходят в отдельную папку, и смысл теряется. Работают узкие условия:

  • появилась новая проблема, которой не было раньше (это главный сигнал);
  • проблема, помеченная как решённая, вернулась (регрессия после деплоя);
  • частота выросла резко — например, больше 50 событий за 10 минут;
  • ошибка затронула больше N уникальных пользователей.

Отдельное правило стоит завести на платёжный и заказной контур — там цена незамеченной ошибки выше. И помните, что часть «ошибок» — на самом деле проблемы производительности: таймауты запросов к базе прилетают в трекер как исключения, а лечатся не в PHP-коде — см. заметку про индексы MySQL и чек-лист ускорения WordPress.

Чек-лист

  1. display_errors = Off, log_errors = On, error_reporting = E_ALL, ротация лога через logrotate.
  2. Подключён SDK, инициализация стоит в самой ранней точке входа.
  3. Заданы environment и release; релиз обновляется автоматически при деплое.
  4. DSN лежит в переменных окружения, а не в коде репозитория.
  5. send_default_pii выключен, чувствительные поля вычищены в before_send.
  6. Шум отфильтрован: боты, 404, известные исключения — в ignore_exceptions.
  7. Алерты настроены на новые проблемы, регрессии и всплески, а не на каждое событие.
  8. Есть договорённость, кто и когда разбирает очередь, — иначе всё предыдущее бесполезно.

Вывод

Мониторинг ошибок делается за час, а окупается при первом же инциденте. Начать можно с малого: поставить SDK, задать окружение и релиз, включить одно правило «новая ошибка в production» и месяц просто смотреть, что приходит. Почти всегда выясняется, что на проде годами живут две-три ошибки, о которых никто не знал, — и чинятся они за вечер.

Частые вопросы

Q.Зачем нужен трекер ошибок, если уже есть error.log?

Лог фиксирует факт ошибки, но не превращает его в задачу. Трекер группирует одинаковые события в одну проблему, считает частоту и число затронутых пользователей, добавляет контекст (URL, параметры запроса, стек с кодом, релиз) и сам присылает уведомление — вместо того чтобы ждать, пока кто-то откроет файл.

Q.Сколько стоит Sentry и хватит ли бесплатного плана?

Бесплатный план Developer даёт 5 000 ошибок в месяц и одного пользователя, Team — 26 $ в месяц за 50 000 ошибок, Business — 80 $. Считаются события, а не уникальные проблемы, поэтому один зацикленный крон способен выесть месячную квоту за часы. Для небольшого сайта бесплатного плана обычно хватает при условии, что шум отфильтрован через before_send.

Q.Можно ли поднять трекер ошибок на своём сервере?

Да, и сразу тремя способами. Self-hosted Sentry даёт полную функциональность, но требует 4 ядра, 16 ГБ RAM плюс swap и быстрый диск. GlitchTip — совместимая реализация того же API, которой хватает 256–512 МБ RAM и PostgreSQL 14+. Bugsink — ещё один лёгкий вариант. Во всех случаях в коде меняется только DSN.

Q.Не утекут ли персональные данные пользователей в трекер?

Опция send_default_pii по умолчанию выключена, то есть IP, cookies и заголовки не отправляются. Всё остальное чувствительное вычищают в колбэке before_send, а ненужные события там же отсекают возвратом null. Если в запросах есть персональные данные, отправка в зарубежное облако считается трансграничной передачей — в этом случае разумнее поднять трекер на своём сервере.

Источники

Предыдущая
Логи WordPress: как включить и читать

Читайте также

Тёмный рабочий стол разработчика: крупным планом монитор с бегущим логом в терминале, среди серых строк выделяются красные строки ошибок; на заднем плане размытая серверная стойкаЗаметки
10 сентября 2026 г.

Логи WordPress: как включить и читать

Три строки в wp-config.php включают запись всех ошибок PHP в файл. Разбираем, куда попадает debug.log, как закрыть его от посторонних, что означают Fatal error, Warning и Deprecated и как за минуту найти виновный плагин.

Читать →
Рабочее место разработчика ночью: на переднем мониторе — редактор с PHP-кодом, на втором — страница товара интернет-магазина; свет от экранов падает на клавиатуру и столЗаметки
8 сентября 2026 г.

Хуки WooCommerce: как менять поведение магазина

Хуки — штатный способ менять магазин на WooCommerce, не трогая файлы плагина. Разбираем действия и фильтры, приоритеты, remove_action, пять рабочих рецептов и главную ловушку 2026 года — блочные «Корзину» и «Оформление заказа», где классические хуки просто не вызываются.

Читать →
Тёмный рабочий стол разработчика крупным планом: экран ноутбука с открытым терминалом и командной строкой, рядом механическая клавиатура, кабель и сетевой коммутатор в расфокусеЗаметки
7 сентября 2026 г.

WP-CLI: команды на каждый день

Рабочий набор команд WP-CLI для повседневного обслуживания сайтов: установка, обновления ядра и плагинов, дампы базы, замена домена без порчи сериализованных данных, пользователи, медиа, кеш, cron, алиасы для нескольких окружений — и грабли, на которые наступают чаще всего.

Читать →