嗨,老铁,欢迎来到我的博客!

如果觉得我的内容还不错的话,可以关注下我在 segmentfault.com 上的直播。我主要从事 PHP 和 Java 方面的开发,《深入 PHP 内核》作者之一。

[视频直播] PHP 进阶之路 - 亿级 pv 网站架构的技术细节与套路 直播中我将毫无保留的分享我这六年的全部工作经验和踩坑的故事,以及会穿插着一些面试中的 考点难点加分点

菜单开关

周梦康 发表于 2017-09-08 1393 次浏览 标签 : PHP框架笔记php

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

我现在的做法是

  1. 开启了application.dispatcher.catchException = true
  2. 增加了ErrorController,参考 http://www.php.net/manual/en/yaf-dispatcher.catchexception.php
  3. 在最外层 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

嗨,老铁,欢迎来到我的博客!

如果觉得我的内容还不错的话,可以关注下我在 segmentfault.com 上的直播。我主要从事 PHP 和 Java 方面的开发,《深入 PHP 内核》作者之一。

[视频直播] PHP 进阶之路 - 亿级 pv 网站架构的技术细节与套路 直播中我将毫无保留的分享我这六年的全部工作经验和踩坑的故事,以及会穿插着一些面试中的 考点难点加分点

评论列表

回复 大头 2017-11-15 20:17:07
还有个 error_log
回复 周梦康 2018-02-03 12:36:54
回复大头: error_log 不属于我讨论的范围,可能。