2018.03.21 更新:https://mengkang.net/1198.html
ErrorException
弥补了error_get_last()
错误信息严重不足的问题。
在业务代码里接管日志,php 提供了三个函数来做这些事,分别是
register_shutdown_function();
set_error_handler();
set_exception_handler();
使用 set_error_handler
在项目入口处添加如下代码
set_error_handler('yq_error_handler');
function yq_error_handler($errno, $errstr, $errfile, $errline){
yq_halt(sprintf("%s %s 第 %d 行\n",$errstr,$errfile,$errline));
}
function yq_halt($error){
if (ENV_TAG == ENV_DEV){
echo $error;
}else{
// 分布式日志收集
}
}
运行,收集到一些日志
filesize(): stat failed for debug_cache /xxxx/Log.php 第 106 行
mysql_escape_string(): This function is deprecated; use mysql_real_escape_string() instead. /xxxx/Mysql.php 第 193 行
mysql_connect(): The mysql extension is deprecated and will be removed in the future: use mysqli or PDO instead /xxxx/mysql_driver.php 第 186 行
mysql_escape_string(): This function is deprecated; use mysql_real_escape_string() instead. /xxxx/mysql_db.php 第 243 行
第一个错误
通过查看代码发现/xxxx/Log.php 第 106 行
里面使用了@
符号来抑制错误,实际这个错误是E_WARNING
级别。
也就是说@
对自定义错误处理,是无效的。还是会收到错误提示。
deprecated 废弃类错误
这个我暂时不想大面积改造,所以先不动那块。
这类错误在 PHP 里常量定义为E_DEPRECATED
,所以这类错误我不想接管,暂时由它去吧。
改造上面的函数
function yq_error_handler($errno, $errstr, $errfile, $errline){
switch ($errno) {
case E_DEPRECATED:
break;
default:
yq_halt(sprintf("%s %s 第 %d 行\n",$errstr,$errfile,$errline));
break;
}
}
方式比较保守,暂时只是不显示废弃类的错误。
现在在某个控制器里面把一行代码末尾的;
去掉,再次运行,发现页面没有输出错误,然后查看服务器上的 php 错误日志,发现里面有记录
[08-Sep-2017 10:14:47 Asia/Chongqing] PHP Parse error: syntax error, unexpected 'if' (T_IF) in xxx.php on line 175
也就是说set_error_handler
无法接管E_PARSE
类的错误。
所以,我们通过register_shutdown_function
来接管错误日志的收集,信息更加全面。
值得注意的是
register_shutdown_function
的用意是在脚本正常退出或显示调用exit时,执行注册的函数。
是脚本运行(run-time not parse-time)出错退出时,才能使用。如果在调用register_shutdown_function
的同一文件的里面有语法错误,是无法注册的,但是我们项目一般都是分多个文件的,这样就其他文件里有语法错误,也能捕获了
https://segmentfault.com/a/1190000007182984 这篇文章举例比较详细
使用 register_shutdown_function
register_shutdown_function("yq_fatal_handler");
function yq_fatal_handler(){
if ($e = error_get_last()) {
yq_halt(sprintf("%s %s 第 %d 行\n",$e['message'],$e['file'],$e['line']));
}
}
使用 ErrorException
替代 error_get_last()
获取更多的错误信息:https://mengkang.net/1198.html
使用 set_exception_handler
如果在yaf
里如果开启了
application.dispatcher.catchException = true
则set_exception_handler
无法接管异常。参考官方手册 http://php.net/manual/zh/yaf-dispatcher.seterrorhandler.php
我现在的做法是
- 开启了
application.dispatcher.catchException = true
- 增加了
ErrorController
,参考 http://www.php.net/manual/en/yaf-dispatcher.catchexception.php - 在最外层 catch 异常,统一收集异常日志
try {
$app->bootstrap()->run();
} catch (Exception $e) {
// 统计收集异常日志
}
日志收集字段的完善
本来我只记录了默认的异常日志和错误日志,如下:
public static function slsExceptionLog(\Exception $exception){
$content = [
"file" => $exception->getFile(),
"exception_code" => $exception->getCode(),
"line" => $exception->getLine(),
"message" => $exception->getMessage(),
"trace" => $exception->getTraceAsString()
];
return self::slsLog($content,"Exception");
}
public static function slsErrorLog($error){
$content = [
"file" => $error['file'],
"error_type" => $error['type'],
"line" => $error['line'],
"message" => $error['message'],
];
return self::slsLog($content,"Error");
}
发现不太好追踪问题,比如这样的日志
__source__: xxx.xxx.xxx.xxx
__topic__: Error
error_type: 1
file: /xxx/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php
line: 33
message: Maximum execution time of 30 seconds exceeded
根据我们的系统属性,为 Web 为主,也有一些命令行执行的计划任务,所以做了些改进,方便回放请求,定位具体问题。
public static function slsExceptionLog(\Exception $exception)
{
$content = [
"file" => $exception->getFile(),
"exception_code" => $exception->getCode(),
"line" => $exception->getLine(),
"message" => $exception->getMessage(),
"trace" => $exception->getTraceAsString(),
"request_uri" => isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : "",
"request_method" => isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : "",
"request_post" => json_encode($_POST),
"request_cookie" => $_COOKIE,
"php_sapi" => php_sapi_name(),
];
return self::slsLog($content, "Exception");
}
public static function slsErrorLog($error)
{
$content = [
"file" => $error['file'],
"error_type" => $error['type'],
"line" => $error['line'],
"message" => $error['message'],
"request_uri" => isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : "",
"request_method" => isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : "",
"request_post" => json_encode($_POST),
"request_cookie" => $_COOKIE,
"php_sapi" => php_sapi_name(),
];
return self::slsLog($content, "Error");
}
收集到日志
__source__: xxx.xxx.xxx.xxx
__topic__: Error
error_type: 1
file: /xxx/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php
line: 132
message: Maximum execution time of 30 seconds exceeded
php_sapi: fpm-fcgi
request_cookie: xxxx
request_post: []
request_method: GET
request_uri: /articles/283638
今天看到一篇博客,我觉得总结的比我详细,写的比我的好,记录下 http://www.cnblogs.com/painsOnline/p/5141346.html