Как я должен обрабатывать исключения, генерируемые регистратором при обработке другого исключения? - PullRequest
4 голосов
/ 02 июля 2019

Я работаю над проектом PHP, где я ловлю исключения и регистрирую ошибки, используя Monolog и возвращая удобную для пользователя страницу в качестве ответа.

Проект в настоящее время находится в стадии разработки, поэтому я нахожусьпросто регистрируя ошибки в файле с помощью класса StreamHandler Monolog в каталоге приложения вне досягаемости публики, так как я прогрессирую, я понимаю, что это может произойти сбой, если есть какая-то ошибка ввода-вывода, и поэтому я также буду регистрироваться в базе данных (возможен ElasticSearch) и отправка критических ошибок по электронной почте администратору.

Поскольку я использую StreamHandler, я вижу, что он выдает исключение, если не удается открыть файл.

Теперь, как мне следует обрабатывать этот случай исключения и как его регистрировать, если сам механизм регистрации выходит из строя?

Я могу обработать исключение другим регистратором, который отправляет электронное письмов таких критических ситуациях, но опять же, как мне обработать исключение, выдаваемое почтовой программой?

Я предполагаю, что страница будетзаполнено слишком большим количеством блоков try-catch с регистраторами, разбросанными по всей странице, что выглядело бы ужасно.

Существует ли элегантное, чистое решение, которое не включает слишком много вложенных блоков try-catch, которые используютсяв крупных проектах?(Непопулярные мнения также приветствуются)

Вот код для справки:

try
{
    $routes = require_once(__DIR__.'/Routes.php');

    $router = new RouteFactory($routes, $request, \Skletter\View\ErrorPages::class);
    $router->buildPaths('Skletter\Controller\\', 'Skletter\View\\');

    $app = new Application($injector);
    $app->run($request, $router);
}
catch (InjectionException | InvalidErrorPage | NoHandlerSpecifiedException $e)
{
    $log = new Logger('Resolution');
    try
    {
        $log->pushHandler(new StreamHandler(__DIR__ . '/../app/logs/error.log', Logger::CRITICAL));
        $log->addCritical($e->getMessage(),
            array(
                'Stack Trace' => $e->getTraceAsString()
            ));
    }
    catch (Exception $e)
    {
        echo "No access to log file: ". $e->getMessage();
        // Should I handle this exception by pushing to db or emailing?
        // Can possibly introduce another nested try-catch block 
    }
    finally
    {
        /**
         * @var \Skletter\View\ErrorPageView $errorPage
         */
        $errorPage = $injector->make(\Skletter\View\ErrorPages::class);
        $errorPage->internalError($request)->send();
    }
}

Ответы [ 5 ]

4 голосов
/ 05 июля 2019

Регистрация исключений и уведомление о них - это две задачи, которые должны решаться глобально для всего проекта. Они не должны решаться с помощью блоков try-catch, потому что, как обычно, try-catch следует использовать, чтобы попытаться решить конкретные локальные проблемы, которые сделали исключение (например, изменить данные или попытаться повторить выполнение) или выполнить действия по восстановлению состояния приложения. Регистрация и уведомление об исключениях - это задачи, которые должны решаться с помощью глобального обработчика исключений. В PHP есть собственный механизм для настройки обработчика исключений с функцией set_exception_handler. Например:

function handle_exception(Exception $exception)
{
    //do something, for example, store an exception to log file
}

set_exception_handler('handle_exception');

После настройки обработчика все сгенерированные исключения будут обрабатываться с помощью функции handle_exception(). Например:

function handle_exception(Exception $exception)
{
    echo $exception->getMessage();
}

set_exception_handler('handle_exception');
// some code
throw Exception('Some error was happened');

Кроме того, вы всегда можете отключить текущий обработчик исключений с помощью функции restore_exception_handler .

В вашем случае вы можете создать простой класс обработчика исключений, который будет содержать методы ведения журналов и методы уведомлений, а также реализовать механизм обработки исключений, который выберет необходимый метод. Например:

class ExceptionHandler
{    
    /**
     * Store an exception into a log file         
     * @param Exception $exception the exception that'll be sent
     */
    protected function storeToLog(Exception $exception)
    {}

    /**
     * Send an exception to the email address
     * @param Exception $exception the exception that'll be sent
     */
    protected function sendToEmail(Exception $exception)
    {}

    /**
     * Do some other actions with an exception
     * @param Exception $exception the exception that'll be handled
     */
    protected function doSomething(Exception $exception)
    {}

    /**
     * Handle an exception
     * @param Exception $exception the exception that'll be handled
     */
    public function handle(Exception $exception)
    {
        try {
            // try to store the exception to log file
            $this->storeToLog($exception);
        } catch (Exception $exception) {
            try {
                // if the exception wasn't stored to log file 
                // then try to send the exception via email
                $this->sendToEmail($exception);
            } catch (Exception $exception) {
                // if the exception wasn't stored to log file 
                // and wasn't send via email 
                // then try to do something else
                $this->doSomething($exception);
            }
        }

    }
}

После этого вы можете зарегистрировать этот обработчик

$handler = new ExceptionHandler();
set_exception_handler([$handler, 'handle']);


$routes = require_once(__DIR__.'/Routes.php');

$router = new RouteFactory($routes, $request, \Skletter\View\ErrorPages::class);
$router->buildPaths('Skletter\Controller\\', 'Skletter\View\\');

$app = new Application($injector);
$app->run($request, $router);
0 голосов
/ 11 июля 2019

Нет элегантного решения этой проблемы. Из вашего вопроса я понимаю, что вы создаете библиотеку для обработки исключений, которая охватывает все приложение и создает некоторые побочные эффекты (например, запись в файл журнала). Вопрос в том, как эта библиотека справляется со сценарием, когда эти побочные эффекты (части основной функциональности библиотеки) - терпят неудачу?

Самый простой и интуитивный ответ, на мой взгляд, таков: не обрабатывайте их . Действуй так, как будто твоя библиотека не существует.

Другими словами, отбросьте все исключения, которые вы уловили, но не смогли обработать. Не думайте, что ваша библиотека является единственным обработчиком исключений в приложении. Может быть другая библиотека / абстракция журналирования, которая также захватывает исключения и обрабатывает их не так, как вы (например, отправляйте их по электронной почте вместо записи в файл). Дайте этим другим игрокам шанс обработать исключение.

Предполагая базовый сценарий, в котором ваша библиотека является единственной библиотекой обработки ошибок, которая охватывает проект: ваша библиотека все еще не смогла выполнить свою основную функцию (перехватить ошибку и записать ее в файл), что является фатальной ошибкой, поскольку приложение не может функционировать без своей основной функции. Как и многие другие фатальные ошибки, лучше всего «быстро провалиться, рано провалиться» и передать эти ошибки в PHP для самостоятельной обработки, записав их в свой собственный error_log или отобразив их конечному пользователю ( в зависимости от уровня error_reporting).

В качестве альтернативы вы всегда можете сообщить разработчику об этой возможности проблем с разрешениями файлов или папок и дать разработчику возможность определять бизнес-логику, которая будет выполняться при таких низкоуровневых сбоях. Приведенный вами пример нескольких блоков catch является одним из способов сделать это. Передача необязательной лямбда-функции, которая должна выполняться при сбоях низкого уровня, - это еще одна задача Здесь есть много вариантов.

0 голосов
/ 09 июля 2019

Если бы я был вами, я бы поймал это исключение в событии "kernel.exception" по простой причине: ваш регистратор - E V E R Y W H E R E. Просто напишите слушателю, протестируйте что-то вроде

if ($e instanceof MONOLOG_SPECIFIC_EXCEPTION) { // handle exception here }

Если вы тоже используете команды, проделайте то же самое с событием console.exception.

0 голосов
/ 04 июля 2019

Есть несколько решений, которые вы можете попробовать.

  1. Вы можете обернуть регистратор в другой собственный класс, обрабатывать все исключения и возможные ошибки, и используйте свой класс для ведения журнала.

  2. Иногда невозможно отследить все ошибки, исключения остаются необработанными, и случаются редкие случаи (т. Е. Ошибка ввода-вывода). Вы можете решить с этим жить. В этом случае вы можете использовать решение, которое вы подняли самостоятельно. Воспользуйтесь пакетом ELK, и вы сможете настроить монитор часов на интересующие вас параметры.

0 голосов
/ 04 июля 2019

Я думаю, что в этой ситуации вы должны записать на диск. Затем вы пишете функцию, которая читает из этого файла и в основном делает то, что делал регистратор.

...