您的位置:晶晶的博客>PHP>PHP的错误处理机制与错误日志记录

PHP的错误处理机制与错误日志记录

PHP中的错误简单点儿理解就是导致PHP代码不能正常执行的各种问题的集合,错误是需要被处理的,PHP引擎在执行过程中也会对错误进行各种提示,这种提示其实也是对错误的一种处理,只不过比较粗暴而已。

PHP中与错误有关的内容可大致分为:PHP错误的类型和错误级别有关内容、PHP代码中显式的错误处理机制、PHP代码中的错误信息使用日志形式记录的有关配置内容 等。

PHP中的错误类型

PHP中的错误按PHP内置常量来划分有如下类型:

参考手册:http://php.net/manual/zh/errorfunc.constants.php

错误类型常量 说明
E_ERROR 致命的运行时错误。这类错误一般是不可恢复的,例如内存分配导致的问题。后果是导致脚本终止不再继续运行。
E_WARNING 运行时警告 (非致命错误)。仅给出提示信息,但是脚本不会终止运行。
E_PARSE 编译时语法解析错误,也是致命错误,脚本终止运行。解析错误仅仅由分析器产生。
E_NOTICE 运行时通知(非致命错误)。表示PHP代码中遇到了非合理的但暂时可以忽略的代码错误,譬如:未初始化的变量直接使用-----其实这种情况在其他变量类型严格限定的语言中也是致命的,只不过PHP比较宽容。
E_CORE_ERROR 在PHP初始化启动过程中发生的致命错误。该错误类似E_ERROR,但是是由PHP引擎核心产生的。
E_CORE_WARNING PHP初始化启动过程中发生的警告 (非致命错误) 。类似 E_WARNING,但是是由PHP引擎核心产生的。
E_COMPILE_ERROR 致命编译时错误。类似E_ERROR, 但是是由Zend脚本引擎产生的。
E_COMPILE_WARNING 编译时警告 (非致命错误)。类似 E_WARNING,但是是由Zend脚本引擎产生的。
E_USER_ERROR 用户产生的错误信息。类似 E_ERROR, 但是是由用户自己在代码中使用PHP函数 trigger_error()来产生的。
E_USER_WARNING 用户产生的警告信息。类似 E_WARNING, 但是是由用户自己在代码中使用PHP函数 trigger_error()来产生的。
E_USER_NOTICE 用户产生的通知信息。类似 E_NOTICE, 但是是由用户自己在代码中使用PHP函数 trigger_error()来产生的。
E_STRICT 启用 PHP 对代码的修改建议,以确保代码具有最佳的互操作性和向前兼容性。
E_RECOVERABLE_ERROR 可被捕捉的致命错误。 它表示发生了一个可能非常危险的错误,但是还没有导致PHP引擎处于不稳定的状态。 如果该错误没有被用户自定义句柄捕获 (参见 set_error_handler()),将成为一个 E_ERROR 从而脚本会终止运行。
E_DEPRECATED 运行时通知。启用后将会对在未来版本中可能无法正常工作的代码给出警告。
E_USER_DEPRECATED 用户产生的警告信息。 类似 E_DEPRECATED, 但是是由用户自己在代码中使用PHP函数 trigger_error()来产生的。
E_ALL E_STRICT除外的所有错误和警告信息。

上述错误类型中被着色的11个是需要理解并掌握的。上述的错误类型常量表示的值都是int型的,至于这些常量具体表达的整数是几不重要,更不需要记忆,只需要知道有叫这个名儿的常量并且是个int型即可,在以后很长一段时间内这些常量名会一直存在,但这些常量所代表的数字是不是会变化是不确定的;如果面试题中遇到纯粹考察记忆这些常量代表的数字是几的,强烈建议果断扔笔走人,再找下一家。

上述的错误类型也是一种错误级别,这样可以指定编写的PHP代码报告错误的类型和级别,在PHP配置文件php.ini中可以通过error_reporting字段来指定这个级别,这样在PHP代码执行时就会按照你该字段指定的错误类型级别来显示错误信息(或者记录错误日志);当然除了php配置文件中指定这个错误日志记录的级别外,php代码中还可以使用error_reporting() 函数来设定这么一个错误级别,很多时候会有:E_ALL & ~E_NOTICE这么一种写法,就表示E_ALL错误类型中除了E_NOTICE外其他的错误信息都显示或者记录。

PHP中的错误是需要被处理的,也就是说PHP代码中需要对可能出现或存在的错误进行统一的后续处理,譬如对访客进行友好的提示,而不是直接把开发中显示的错误信息抛给访客;更何况PHP的错误提示信息也仅仅是为了开发者更敏捷的开发,线上环境的代码出现了错误也最好不要直接显示这些错误信息到访客的浏览器屏幕上。PHP提供有set_error_handler()函数来自定义错误处理回调函数,来统一处理PHP执行过程中出现了错误之后的处理逻辑,其实PHP默认的出现错误后给出错误提示信息这个过程也可以理解成为一种错误处理机制,只不过这种机制由PHP引擎内部提供,提示比较粗暴、界面比较丑陋而已。

PHP Error类型

上图是4种PHP错误类型中PHP内置的错误处理机制生成的错误信息,因为要展现在浏览器页面中,故而输出的实际上是Html片段,由浏览器渲染后看到丑陋的错误提示效果,截图中有浏览器渲染后的效果和实际输出的Html源码片段。对于开发过程中这些错误提示信息已经绰绰有余:错误类型、错误原因、以及错误的文件和行号都有了;但对于线上环境来说这种错误提示信息对访客就不友好了,而且也没必要将这么多的PHP错误有关的信息都告知给访客,再说访客也不一定看得懂啊!但从开发者角度来看,线上环境出现错误后,这些错误信息既不能暴露给普通访客又不能直接丢掉,背后还需要用各种手段保留住这些错误信息,以便于敏捷的修改或完善这些出错了的代码。

PHP中错误日志有关的配置

PHP中有关错误和错误日志的配置项如下表所示:

名字 默认值 作用说明
error_reporting NULL 设定错误报告的级别和类型,例如:E_ALL & ~E_NOTICE
display_errors "1" 设定是否在输出结果中显示错误信息,换种说法:是否将错误信息显示在浏览器中。取值Off、On或stderr
display_startup_errors "0"
log_errors "0" 设定是否将PHP中的错误信息记录到web server的日志中或者下方error_log置顶的文件中。取值Off、On
log_errors_max_len "1024" 设定记录log_errors的最大字节数;log_errors配置值On时生效,记录至error_log中时会添加有关错误源的信息。默认值为1024,如果设置为0表示不限长度。
ignore_repeated_errors "0"
ignore_repeated_source "0"
report_memleaks "1"
track_errors "0"
html_errors "1"
xmlrpc_errors "0"
xmlrpc_error_number "0"
docref_root ""
docref_ext ""
error_prepend_string NULL
error_append_string NULL
error_log NULL 当个log_errors处于开启状态时,配置PHP脚本中的错误信息记录的文件位置;该文件必须是web-server运行用户可写。如果特殊值 syslog 被设置,则将错误信息发送到系统的syslog系统进行日志记录。经过测试:若此处设置的文件位置运行web-server的的linux系统用户无写权限的话,会被当做没有设置这个值。如果该配置没有设置,则错误信息会被发送到SAPI错误记录器,最终被web-server所记录,例如:nginx的error_log,apache的error_log。

参考文档:http://php.net/manual/zh/errorfunc.configuration.php

有关配置又是一大堆,不要被唬住了,上方着色的几项需要掌握,其他的理解清楚即可,上方参考文档看一看即可。

首先将PHP的输出错误信息的配置项打开,找到php.ini文件中的display_errors配置项,去掉前面的分号将其值修改为On,这样PHP内置的错误处理机制将在出现错误时将错误信息输出到浏览器中。如果没有修改php.ini的权限,在PHP代码中加上ini_set('display_errors','On');可以部分实现该功能。

PHP代码中统一处理错误

本文不止一次提到:“错误是要被处理的”,开发阶段出现的错误可以通过PHP默认的错误信息进行调试解决,上线后哪些隐藏的错误至少需要避免PHP默认的这种粗暴的错误消息直接暴露给访客,更进一步则需要做一个友好的提示,同时将错误日志保留下来便于及时发现错误并解决。

PHP提供的mixed set_error_handler( callable $error_handler [, int $error_types = E_ALL | E_STRICT ] )函数用于注册一个处理由$error_types指定的错误类型的错误的回调函数,被注册的回调函数中可以完成错误消息的输出、错误日志记录、访客界面友好提示等流程,这些流程由开发者自己定义;传入的回到函数$error_handler的格式为:bool handler ( int $errno , string $errstr [, string $errfile [, int $errline [, array $errcontext ]]] ),如果这个函数返回false,标准错误处理处理程序将会继续调用;各参数的作用和含义如下:

errno 包含了错误的级别,是一个 integer

errstr 包含了错误的信息,是一个 string

errfile 第三个参数是可选的,包含了发生错误的文件名,是一个 string

errline 第四个参数是一个可选项, 包含了错误发生的行号,是一个 integer

errcontext 第五个可选参数, 是一个指向错误发生时活动符号表的array<错误上下文的变量数组>;也就是说,errcontext 会包含错误触发处作用域内所有变量的数组;自定义的错误处理逻辑中不应该修改这个错误上下文。自PHP7.2开始这个参数已经被废弃,所以编写自定义错误处理函数时只用前四个参数即可。

编写好自己的错误处理回调函数,并且mixed set_error_handler( callable $error_handler [, int $error_types = E_ALL | E_STRICT ] )注册之后,当编写的PHP代码中出现了set_error_handler注册时第二个参数指定的错误类型时就会触发你所编写的错误处理回调函数$error_handler,在这个回调函数里你可以做很多事儿,譬如:用一个配置项做标记,标记是开发环境还是生产环境,开发环境就做一个错误信息提示页面直接输入到网页中,生产环境就不能输出这些错误信息,而是要做一个友好的信息提示页面。下面抛砖引玉:

<?php
/**
 * 
 * @authors Jea杨 (JJonline@JJonline.Cn)
 * @date    2017-05-22 10:10:06
 * @version $Id$
 */
// 设定错误报告状态为E_ALL和E_STRICT,也就是所有错误均报告
error_reporting(E_ALL | E_STRICT);
// 设定显示错误日志
ini_set('display_errors','On');
// 定义当前是开发状态还是生产环境状态
define('IS_DEV', true);
// 定义错误处理回调函数,这里用了匿名函数
set_error_handler(function ($errno , $errstr , $errfile, $errline) {
    if(IS_DEV)
    {
        // 如果开发状态做什么提示
        echo '<p>'.$errstr.':'.$errfile.'</p>'."\r\n";
    }else{
        // 如果不是开发状态又做什么提示
        echo '抱歉程序异常';
        // code ... 写错误日志啥滴代码...
    }
    return true;
});

// 下面用2个错误来测试一下
$x / 1;
1 / 0;

// 再用一个代码主动触发的错误
trigger_error('Trigger Error');

网页上输出的结果如下图所示,图中上部分是浏览器渲染后的界面,下部分是实际输出的字符串内容,是一个html片段:

自定义错误处理

上述示例代码中输出了3个错误的所有信息,实际开发中可以在错误处理回调函数中做一个代码终止,这样遇到第一条错误脚本就会终止并输出。

PHP中怎样记录错误日志?

这个问题似乎有点重复了,实则不然。

1、set_error_handler注册回调函数,在回调函数中记录错误日志

当注册了错误处理回调函数之后,若自定义的回调函数没有返回值或者返回值为true,则PHP内置的错误日志记录机制将失效,也就是说php.ini配置文件中log_errors配置值为On,无论error_log配置值是啥都不会有任何日志被记录进这个配置值指向的文件。那么这个日志该怎样记录呢?有两种方式:

第一种:在set_error_handler注册的回调函数中返回一个false,并且log_errors配置值为On,这个时候错误日志将会自动记录至error_log配置值指向的文件(若配置值为syslog则会记录至操作系统的syslog组件的日志里;若配置值为空则会记录至web-server的错误日志里例如nginx的error_log中;若配置值为一个文件路径,且这个文件对web-server的运行用户可写则会将错误日志记录至这个文件,若这个文件对运行web-server的用户不可写,则相当于这个配置值为空)。这种情况下因为自定义的错误处理回调函数返回了false依然会触发PHP内置的错误处理机制,所以出现错误时依据display_errors配置的值,页面中会可能会出现PHP默认的丑陋、粗暴的错误提示信息,所以采用这种方式需要在生产环境中将display_errors配置为Off。还有一个需要留意的问题:若自定义的错误处理回调函数中有终止脚本运行的代码,也就是exitdie,其效果相当于错误处理回调函数没有返回值,亦既也是不会记录错误日志的。

第二种:在set_error_handler注册的回调函数的代码中显式记录错误日志。这种是注册了自定义错误处理机制回调函数之后的推荐的方式,也是许多PHP框架所实现的方式。回调函数的参数已经传递过来错误级别、错误信息、错误文件、错误行号,PHP代码中自主记录这些信息到一个文本文件中即可。怎么记录?有多种方式,一是直接写文件例如file_put_contents,二是调用error_log函数,三是调用syslog函数,需要注意的是syslog函数与操作系统的syslog组件有关,这个与php.ini中的error_log配置项配置为syslog极其类似,留意区别。

2、使用PHP内置的错误处理机制记录错误日志

这种方式是在PHP代码中没有使用set_error_handler注册错误处理回调函数,又希望记录下PHP的错误日志,这种方式灵活使用PHP错误有关配置项即可实现,这也是很古老的一种方式,实现原理就是开发环境下将错误信息显示在网页中,生产环境下网页中不显示错误信息但一旦有错误页面很有可能会白屏或者500错误的页面。

开发环境下display_errors 配置值为On,log_errorserror_log任意,开发的时候错误信息直接显示在浏览器上,当然这里面还有一个错误级别设定的问题;生产环境下display_errors 配置值为Off,错误信息不显示在浏览器中,log_errors配置为On即打开错误日志记录功能,error_log配置为一个web-server运行用户可写的文件或者syslog值,或者留空。

error_log为空或配置的文件web-server运行用户无写权限,错误日志将记录在web-server的错误日志中,例如nginx+php-fpm模式,那么php的错误日志将记录在nginx通过error_log指令指定的日志文件中;

error_log为syslog这个特殊值,那么PHP的错误日志将记录在操作系统的syslog组件的日志文件中,这个依据操作系统不同位置不同,例如:centos6.5下,日志将记录至:/var/log/messages文件中。

以下是配置PHP错误日志记录至3种不同位置的日志情况截图:

error_log配置成了单独的一个文件

error_log配置成了单独的一个文件

error_log配置为空,错误日志记录至nginx的error_log中

error_log配置为空,错误日志记录至nginx的error_log中

error_log配置记录至系统的syslog

error_log配置记录至系统的syslog

set_error_handler注册的回调函数处理错误的局限性

需要注意的是,set_error_handler定义的错误处理回调函数是不能处理以下级别的错误的: E_ERRORE_PARSEE_CORE_ERRORE_CORE_WARNINGE_COMPILE_ERRORE_COMPILE_WARNING,和在调用set_error_handler函数所在的文件中产生的大多数E_STRICT。也就是说set_error_handler注册的回调函数仅能处理Warning、Notice级别,致命错误Fatal Error、语法解析错误Parse Error之类的则无法捕获并处理。

那么致命错误、语法解析错误该如何捕获并处理呢?

两个函数:

void register_shutdown_function ( callable $callback [, mixed $parameter [, mixed $... ]] ) 即注册一个在脚本执行完毕退出(execution on shutdown)时被调用的回调函数。那么这个execution on shutdown时就有多种情况:1、正常执行退出;2、异常也就是出错误后退出;3、代码中运行到了exit(die)后退出。

array error_get_last ( void ) 获取最后一个发生的错误的信息;函数返回一个关联数组,描述了最后错误的信息,以该错误的typemessagefileline为数组的键,这几个键名与set_error_handler注册的回调函数的前四个参数的含义是一致的,分别是:错误类型编号、错误描述消息、错误文件名、发生错误所在的行号;如果该错误由PHP内置函数导致的,message会以该函数名开头;如果还没有错误则返回 NULL

基本原理就出来了,使用register_shutdown_function注册一个在脚本执行完毕退出时执行的回调函数,在回调函数内使用error_get_last 获取最后一个发生的错误的信息,判断这个错误信息是否属于致命错误或解析错误,然后执行接下来的错误逻辑。

由于PHP编译阶段的错误(Parse Error类型)是在PHP还未正式逐行执行时发生,也就是register_shutdown_function注册的回调函数可能还未生效就已发生,这种情况下register_shutdown_function注册的回调函数是不会被执行的。为了避免这种情况,开发的PHP程序被执行的顺序很重要,一般而言需要在PHP代码的较前位置处使用单独文件或类进行set_error_handler注册错误处理回调函数、register_shutdown_function注册脚本退出时执行的回调函数、以及set_exception_handler注册异常回调函数,然后其他的PHP文件在开头位置处include引入或者自动加载。

转载请注明本文标题和链接:《PHP的错误处理机制与错误日志记录

相关推荐

网友评论抢沙发

路人甲 表情
Ctrl+Enter快速提交