Symfony - обработка ошибок с логированием

Итак, есть проблема: в prod-режиме при возникновении ошибки я вижу ее в журнале ошибок 4 раза.

Выходит это потому, что монолог пишет ее 4 раза (см. ниже), но проблема не в монологе, а в том, кто его вызывает:

Если присмотреться, то ошибку пишет:

  • 1 раз \Symfony\Component\HttpKernel\HttpKernel::handleThrowable()
  • 3 раза \Symfony\Component\ErrorHandler\ErrorHandler::handleException()

Казалось бы, можно отключить \Symfony\Component\ErrorHandler\ErrorHandler - например я переопределил его на свой хендлер-заглушку:

<?php
declare(strict_types=1);

namespace Symfony\Component\ErrorHandler;

/**
 * Проблема: при неправильном значении пути к классу в services.yaml мы получаем 4 ошибки об одном и том же, а
 * различаются они только тем, что к основной ошибке дописывается какая-нибудь информация, например:
 * - "message":"Uncaught PHP Exception Error: \"Class \"YaPro\\SeoBundle\\MyListener\" not found\" at App_KernelProdContainer.php line 257"
 * - "message":"Uncaught Error: Class \"YaPro\\SeoBundle\\MyListener\" not found"
 * - "message":"Uncaught PHP Exception Symfony\\Component\\ErrorHandler\\Error\\ClassNotFoundError: \"Attempted to load class \"MyListener\" from namespace \"YaPro\\SeoBundle\".\nDid you forget a \"use\" statement for another namespace?\" at App_KernelProdContainer.php line 257"
 * - "message":"Uncaught Error: Class \"YaPro\\SeoBundle\\MyListener\" not found"
 *
 * Решение: подменять хендлер ошибок
 *
 * Установка производится в конце файла backend/config/autoload.php добавлением строки:
 * $composer->addClassMap(["Symfony\Component\ErrorHandler\ErrorHandler" => __DIR__ . "/MyErrorHandler/ErrorHandler.php"]);
 *
 * Не забываем пересобрать автолоад (а то композер может полагаться на кэш): composer dump-autoload
*/
class ErrorHandler
{
    private int $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
    private int $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
    private int $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE
    private int $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE

    public static function register()
    {
        $handler = new static();
        return $handler;
    }
    public function doNothing()
    {
        // в случае ошибки обработчик ничего не делает, ведь есть ErrorListener-ры (они как раз пишут логи в json)
    }

    // вызывается в Http и Console:
    public function setDefaultLogger()
    {
        // native php stderr, but see the description of doNothing()
    }
    // вызывается в Http и Console:
    public function scopeAt(): int
    {
        return $this->scopedErrors;
    }
    // вызывается в Http и Console:
    public function throwAt(): int
    {
        return $this->thrownErrors;
    }
    // вызывается только в Http:
    public function screamAt(): int // int $levels, bool $replace = false
    {
        return $this->screamedErrors;
    }

    public function setExceptionHandler(): ?callable
    {
        return null; // я ставил брейкпоинт в этой строке и за месяц работы этот метод ни разу не вызывался
    }
}

Отлично, отключили, но получаем другую проблему - проблему в php (моя версия падает и печатает объект события, в котором все ENV-переменные со значениями, короче компрометация данных).

Попробуем заставить php не печатать этот объект (вообще не понятно, с чего он взял, что его нужно распечатать):

display_errors = off  (не помогает)
display_startup_errors = off  (не помогает)
log_errors = off  (не помогает)

Все, настройки связанные с выводом ошибок закончились - ничего не помогло (php печатает бесконечно большое тело ответа и даже невозможно понять, это stderr или stdout).

Поэтому, делаю вывод:

  1. Symfony не трогаю
  2. При APP_ENV=prod && PHP_SAPI === 'fpm-fcgi' мой монолог-хендлер будет писать ошибку в лог + останавливать php:
http_response_code(500);
echo 'Sorry';
exit(123);

Если что, вот реализация моего монолог-хендлера.


11.09.2021 07:08

Комментарии

Комментариев пока нет.


Copyrights © 2007 - 2026 YaPro.Ru