首先,我想赞扬您查看 PHP 中的标准错误方法。不幸的是error_log
,您发现了一些限制。
这是一个很长的答案,请继续阅读以了解:
- 错误
- 直接记录错误
trigger_error
与set_error_handler
- 好的错误变坏的地方 - 致命错误。
- 例外
- 代码
TL;DR用于trigger_error
引发错误并set_error_handler
记录它们。
1. 错误
当程序中的事情没有按预期进行时,您通常会希望引发错误以便通知某人或某事。错误是指程序可能继续运行,但发生了值得注意的、可能有害或错误的情况。此时,许多人希望使用他们选择的日志记录包立即记录错误。我相信这是完全错误的做法。我建议使用trigger_error
来引发错误,以便可以使用set_error_handler
. 让我们比较这些选项:
直接记录错误
因此,您选择了您的日志记录包。现在,您可以在代码中出现错误的任何地方将调用传播到您的记录器。让我们看一下您可能拨打的单个电话(我将使用与 Jack 回答中的记录器类似的记录器):
Logger::getLogger('standard')->error('Ouch, this hurts');
你需要什么来运行这段代码?
类:记录器
方法:getLogger
返回:带有方法“错误”的对象
这些是使用此代码所需的依赖项。每个想要重用此代码的人都必须提供这些依赖项。这意味着标准的 PHP 配置将不再足以重用您的代码。在最好的情况下,使用依赖注入,您仍然需要将记录器对象传递到所有可能发出错误的代码中。
此外,除了代码负责的任何事情之外,它还负责记录错误。这违背了单一职责原则。
我们可以看到直接记录错误是不好的。
trigger_error 救援
PHP 有一个被称为函数的函数trigger_error
,它可以像标准函数一样用来引发错误。与它一起使用的错误级别在错误级别常量中定义。作为用户,您必须使用用户错误之一:E_USER_ERROR
或E_USER_WARNING
默认值E_USER_NOTICE
(其他错误级别保留用于标准功能等)。使用标准 PHP 函数来引发错误允许代码在任何标准 PHP 安装中重复使用!我们的代码不再负责记录错误(只确保它被引发)。
使用trigger_error
我们只执行一半的错误记录过程(引发错误)并将响应错误的责任留给接下来将介绍的错误处理程序。
错误处理程序
我们使用该函数设置了一个自定义错误处理程序set_error_handler
(参见代码设置)。此自定义错误处理程序取代了标准 PHP 错误处理程序,后者通常根据 PHP 配置设置在 Web 服务器错误日志中记录消息。我们仍然可以通过false
在自定义错误处理程序中返回来使用这个标准错误处理程序。
自定义错误处理程序只有一个职责:响应错误(包括您想要执行的任何日志记录)。在自定义错误处理程序中,您可以完全访问系统,并且可以运行您想要的任何类型的日志记录。几乎任何使用观察者设计模式的记录器都可以(我不打算深入讨论,因为我认为它是次要的)。这应该允许您挂接新的日志观察器以将输出发送到您需要的地方。
您可以完全控制在代码的单个可维护部分中对错误执行您喜欢的操作。现在可以在项目之间或在单个项目中从页面到页面快速轻松地更改错误记录。有趣的是,即使@
被抑制的错误也会以 0 的值进入自定义错误处理程序errno
,如果error_reporting
尊重掩码,则不应报告。
当好的错误变坏时 - 致命错误
无法从某些错误继续。自定义错误处理程序无法处理以下错误级别:E_ERROR
, E_PARSE
, E_CORE_ERROR
, E_CORE_WARNING
, E_COMPILE_ERROR
, E_COMPILE_WARNING
。当这些类型的错误由标准函数调用触发时,自定义错误处理程序将被跳过并且系统关闭。这可以通过以下方式生成:
call_this_function_that_obviously_does_not_exist_or_was_misspelt();
这是一个严重的错误!无法恢复,系统即将关闭。我们唯一的选择是register_shutdown_function
与关闭达成协议。但是,只要脚本完成(成功和不成功),就会执行此函数。当最后一个错误是致命错误时,使用这个和error_get_last
一些基本信息可以记录(此时系统几乎关闭)。发送正确的状态代码并显示您选择的内部服务器错误类型页面也很有用。
2. 例外
可以以与基本错误非常相似的方式处理异常。trigger_error
您的代码将抛出异常而不是异常(手动使用throw new Exception
或来自标准函数调用)。用于set_exception_handler
定义要用于处理异常的回调。
声压级
标准 PHP 库 (SPL) 提供了异常。它们是我引发异常的首选方式,因为trigger_error
它们是 PHP 的标准部分,不会为您的代码引入额外的依赖项。
拿他们怎么办?
抛出异常时,可以进行三种选择:
- 抓住它并修复它(然后代码继续,好像没有发生任何不好的事情一样)。
- 抓住它,附加有用的信息并重新抛出它。
- 让它冒泡到更高的水平。
在堆栈的每一层,都会做出这些选择。最终,一旦它冒泡到最高级别,您设置的回调set_exception_handler
将被执行。这是您的日志记录代码所属的地方(出于与错误处理相同的原因),而不是分布在catch
代码中的整个语句中。
3.代码
设置
错误处理程序
function errorHandler($errno , $errstr, $errfile, $errline, $errcontext)
{
// Perform your error handling here, respecting error_reporting() and
// $errno. This is where you can log the errors. The choice of logger
// that you use is based on your preference. So long as it implements
// the observer pattern you will be able to easily add logging for any
// type of output you desire.
}
$previousErrorHandler = set_error_handler('errorHandler');
异常处理程序
function exceptionHandler($e)
{
// Perform your exception handling here.
}
$previousExceptionHandler = set_exception_handler('exceptionHandler');
关机功能
function shutdownFunction()
{
$err = error_get_last();
if (!isset($err))
{
return;
}
$handledErrorTypes = array(
E_USER_ERROR => 'USER ERROR',
E_ERROR => 'ERROR',
E_PARSE => 'PARSE',
E_CORE_ERROR => 'CORE_ERROR',
E_CORE_WARNING => 'CORE_WARNING',
E_COMPILE_ERROR => 'COMPILE_ERROR',
E_COMPILE_WARNING => 'COMPILE_WARNING');
// If our last error wasn't fatal then this must be a normal shutdown.
if (!isset($handledErrorTypes[$err['type']]))
{
return;
}
if (!headers_sent())
{
header('HTTP/1.1 500 Internal Server Error');
}
// Perform simple logging here.
}
register_shutdown_function('shutdownFunction');
用法
错误
// Notices.
trigger_error('Disk space is below 20%.', E_USER_NOTICE);
trigger_error('Disk space is below 20%.'); // Defaults to E_USER_NOTICE
// Warnings.
fopen('BAD_ARGS'); // E_WARNING fopen() expects at least 2 parameters, 1 given
trigger_error('Warning, this mode could be dangerous', E_USER_WARNING);
// Fatal Errors.
// This function has not been defined and so a fatal error is generated that
// does not reach the custom error handler.
this_function_has_not_been_defined();
// Execution does not reach this point.
// The following will be received by the custom error handler but is fatal.
trigger_error('Error in the code, cannot continue.', E_USER_ERROR);
// Execution does not reach this point.
例外
之前的三个选项中的每一个都在此处以通用方式列出,修复它,附加到它并让它冒泡。
1 可修复:
try
{
$value = code_that_can_generate_exception();
}
catch (Exception $e)
{
// We decide to emit a notice here (a warning could also be used).
trigger_error('We had to use the default value instead of ' .
'code_that_can_generate_exception\'s', E_USER_NOTICE);
// Fix the exception.
$value = DEFAULT_VALUE;
}
// Code continues executing happily here.
2 附加:
看下面怎么code_that_can_generate_exception()
不知道$context
。此级别的 catch 块有更多信息,如果通过重新抛出它有用,它可以附加到异常中。
try
{
$context = 'foo';
$value = code_that_can_generate_exception();
}
catch (Exception $e)
{
// Raise another exception, with extra information and the existing
// exception set as the previous exception.
throw new Exception('Context: ' . $context, 0, $e);
}
3让它冒泡:
// Don't catch it.