【问题标题】:How should I handle exceptions thrown by logger while handling another exception?在处理另一个异常时,我应该如何处理记录器抛出的异常?
【发布时间】:2019-11-12 22:00:16
【问题描述】:

我正在开发一个 PHP 项目,我在该项目中使用 Monolog 捕获异常并记录错误,并返回一个用户友好的页面作为响应。

该项目目前处于婴儿阶段,所以我只是使用 Monolog 的 StreamHandler 类将错误记录到一个文件中,该文件位于公众无法访问的应用目录中,随着我的进展,我意识到如果有 IO 这可能会失败某种错误,因此我还将记录到数据库(可能是 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();
    }
}

【问题讨论】:

  • 你用的是什么版本的PHP?
  • @maximfedorov PHP 7.3

标签: php exception logging try-catch


【解决方案1】:

异常记录和通知是两个必须为整个项目全局解决的任务。它们不应该通过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');

配置handler后,所有抛出的异常都会用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

$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);

【讨论】:

  • >PHP 有一个本地机制来配置一个带有 set_error_handler 函数的全局异常处理程序。你的意思是set_exception_handler?在那种情况下,不应该将其用作未捕获异常的后备,而不是全局处理所有内容吗? PHP 文档说明了这样的意图: >如果在 try/catch 块中未捕获到异常,则设置默认异常处理程序。调用 exception_handler 后将停止执行。
  • @2dsharp 我的意思是 set_exception_handler。我修正了它的错误。是的,异常处理程序用于处理未捕获的异常,但您不应使用帮助try-catch 捕获异常以记录或通知它们。 Try-catch 应该用于尝试解决发生异常的具体问题(例如,修改数据或尝试重复执行)或执行操作以恢复应用程序状态
【解决方案2】:

我认为在这种情况下,您应该写入磁盘。然后你编写一个从该文件中读取的函数,并且基本上完成了记录器所做的事情。

【讨论】:

    【解决方案3】:

    您可以尝试一些解决方案。

    1. 您可以将记录器包装在您自己的另一个类中,处理所有 那里的异常和可能的错误,并使用您的类进行日志记录。

    2. 有时不可能捕获所有错误,异常未处理并且极少数情况发生(即 IO 错误),您可以决定接受它。 在这种情况下,您可以使用自己提出的解决方案。使用 ELK 包,您将能够根据您感兴趣的参数配置监视监视器。

    【讨论】:

      【解决方案4】:

      如果我是你,我会在“kernel.exception”事件中捕获该异常,原因很简单:你的记录器是 E V E R Y W H E R E。 只需编写一个监听器,测试类似

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

      如果您也使用命令,请对“console.exception”事件执行相同操作。

      【讨论】:

        【解决方案5】:

        这个问题没有优雅的解决方案。根据您的问题,我了解到您正在创建一个用于异常处理的库,该库包含整个应用程序并产生一些副作用(例如写入日志文件)。问题是:这个库如何处理这些副作用(库的核心功能的一部分)失败的场景?

        在我看来,最简单和最直观的答案是:不要处理它们。就当你的图书馆不存在一样。

        换句话说,重新抛出您捕获但未能处理的任何异常。不要假设您的库是应用程序的唯一异常处理程序。在您的之上可能还有另一个日志库/抽象,它也捕获异常并以与您不同的方式处理它们(例如,通过电子邮件发送它们而不是写入文件)。让其他玩家有机会处理异常。

        假设您的库是包装项目的唯一错误处理库的基本场景:您的库仍然未能执行其核心功能(捕获错误并将其记录到文件中),这是一个致命错误,因为应用程序没有它的核心功能就无法运作。像许多其他致命错误一样,最好“快速失败,尽早失败”并将这些错误传递给 PHP 自行处理,方法是将它们写入自己的 error_log 或将它们显示给最终用户(取决于 error_reporting级别)。

        或者,您始终可以让开发人员意识到文件或文件夹权限问题的可能性,并让开发人员能够定义要在此类较低级别故障时执行的业务逻辑。您提供的多个 catch 块示例是执行此操作的一种方法。传入一个可选的 lambda 函数以在低级故障时执行是另一个。这里有很多选择。

        【讨论】:

          猜你喜欢
          • 2013-07-24
          • 2022-01-18
          • 2015-09-26
          • 1970-01-01
          • 1970-01-01
          • 2021-04-01
          • 1970-01-01
          • 2016-08-14
          • 1970-01-01
          相关资源
          最近更新 更多