我正在一个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();
}
}
答案 0 :(得分:4)
记录异常和有关异常的通知是整个项目必须全局解决的两个任务。不应使用帮助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);
答案 1 :(得分:0)
我认为在这种情况下,您应该写到磁盘上。然后,您编写一个从该文件读取的函数,并且基本上完成记录器的工作。
答案 2 :(得分:0)
您可以尝试一些解决方案。
您可以将记录器包装到您自己的另一个类中,处理所有 异常和可能的错误,并使用您的类进行记录。
有时无法捕获所有错误,没有处理异常,发生罕见情况(即IO错误),您可以决定忍受这个错误。 在这种情况下,您可以使用自己提出的解决方案。使用ELK软件包,您将可以在自己感兴趣的参数上配置监视监视器。
答案 3 :(得分:0)
如果我是你,我会在“ kernel.exception”事件中捕获该异常,原因很简单:您的记录器为E V E R Y W H E RE。 只需编写一个侦听器,测试
if ($e instanceof MONOLOG_SPECIFIC_EXCEPTION) {
// handle exception here
}
如果您也使用命令,请对“ console.exception”事件执行相同的操作。
答案 4 :(得分:0)
没有完美的解决方案。根据您的问题,我知道您正在创建一个用于异常处理的库,该库包装了整个应用程序并产生了一些副作用(例如,写入日志文件)。问题是:该库如何处理这些副作用(库的部分核心功能)失败的情况?
我认为,最简单,最直观的答案是:不要处理。就像您的图书馆不存在一样。
换句话说,重新抛出您捕获但无法处理的所有异常。不要以为您的库是应用程序的唯一异常处理程序。您可能还有另一个日志记录库/摘要,它可以捕获异常并以与您不同的方式处理异常(例如,通过电子邮件发送异常,而不是写入文件)。给其他玩家一个处理异常的机会。
假设基本情况是您的库是包装项目的唯一错误处理库:您的库仍然无法执行其核心功能(捕获错误并将其记录到文件中),这是致命错误,因为应用程序没有核心功能就无法运作。像许多其他致命错误一样,最好“快速失败,尽早失败”,并将这些错误传递给PHP自行处理,方法是将它们写入自己的error_log或将其显示给最终用户(具体取决于错误报告级别)。
或者,您始终可以使开发人员意识到文件或文件夹权限问题的可能性,并使开发人员可以定义在此类较低级别的故障上执行的业务逻辑。您提供的多个catch块示例是执行此操作的一种方法。传递可选的lambda函数以在低级别故障时执行。这里有很多选择。