In daily development, most people’s approach is to enable it in the development environment Debug mode, turn off debug mode in the production environment. You can check various errors and exceptions during development, but turn off the error display online.
The above situation seems very scientific. Some people explain that it is very safe. Others cannot see the error and avoid leaking important information...
But have you ever encountered this situation? It works fine offline, but it doesn’t work when you go online and you can’t find the reason...
A script has been running for a long time without any problems. One day it suddenly stopped, and then there was no record for whatever reason...
A payment was made online. Others clearly paid, but we did not record it. It is good to experiment by ourselves...
All of the above are because everyone turned off error messages and did not record errors and exceptions in the log, making it difficult to track randomly occurring errors. This creates a contradiction, that is, not to display errors, but also to track errors. How is this achieved?
The above problems can be realized through PHP's error and exception mechanism and its built-in functions 'set_exception_handler', 'set_error_handler', 'register_shutdown_function'
'set_exception_handler' function is used to intercept various uncaught exceptions and then hand them over to user-defined methods for processing
The 'set_error_handler' function can intercept various errors and then handle them in a user-defined way
The 'register_shutdown_function' function is a function called at the end of the PHP script. Together with 'error_get_last', you can get the last fatal error
The general idea is to intercept errors, exceptions, and fatal errors and hand them over to our custom methods for processing. We identify whether these errors and exceptions are fatal. If so, record them in the database or file system, and then use scripts Continuously scan these logs, and if serious errors are found, immediately send an email or text message to alert the police
First we define an error interception class, which is used to intercept errors and exceptions and process them with our own defined processing methods. This class is placed in the file named 'errorHandler.class.php', and the code is as follows
/** * 文件名称:baseErrorHandler.class.php * 摘 要:错误拦截器父类 */ require 'errorHandlerException.class.php';//异常类 class errorHandler { public $argvs = array(); public $memoryReserveSize = 262144;//备用内存大小 private $_memoryReserve;//备用内存 /** * 方 法:注册自定义错误、异常拦截器 * 参 数:void * 返 回:void */ public function register() { ini_set('display_errors', 0); set_exception_handler(array($this, 'handleException'));//截获未捕获的异常 set_error_handler(array($this, 'handleError'));//截获各种错误 此处切不可掉换位置 //留下备用内存 供后面拦截致命错误使用 $this->memoryReserveSize > 0 && $this->_memoryReserve = str_repeat('x', $this->memoryReserveSize); register_shutdown_function(array($this, 'handleFatalError'));//截获致命性错误 } /** * 方 法:取消自定义错误、异常拦截器 * 参 数:void * 返 回:void */ public function unregister() { restore_error_handler(); restore_exception_handler(); } /** * 方 法:处理截获的未捕获的异常 * 参 数:Exception $exception * 返 回:void */ public function handleException($exception) { $this->unregister(); try { $this->logException($exception); exit(1); } catch(Exception $e) { exit(1); } } /** * 方 法:处理截获的错误 * 参 数:int $code 错误代码 * 参 数:string $message 错误信息 * 参 数:string $file 错误文件 * 参 数:int $line 错误的行数 * 返 回:boolean */ public function handleError($code, $message, $file, $line) { //该处思想是将错误变成异常抛出 统一交给异常处理函数进行处理 if((error_reporting() & $code) && !in_array($code, array(E_NOTICE, E_WARNING, E_USER_NOTICE, E_USER_WARNING, E_DEPRECATED))) {//此处只记录严重的错误 对于各种WARNING NOTICE不作处理 $exception = new errorHandlerException($message, $code, $code, $file, $line); $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); array_shift($trace);//trace的第一个元素为当前对象 移除 foreach($trace as $frame) { if($frame['function'] == '__toString') {//如果错误出现在 __toString 方法中 不抛出任何异常 $this->handleException($exception); exit(1); } } throw $exception; } return false; } /** * 方 法:截获致命性错误 * 参 数:void * 返 回:void */ public function handleFatalError() { unset($this->_memoryReserve);//释放内存供下面处理程序使用 $error = error_get_last();//最后一条错误信息 if(errorHandlerException::isFatalError($error)) {//如果是致命错误进行处理 $exception = new errorHandlerException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']); $this->logException($exception); exit(1); } } /** * 方 法:获取服务器IP * 参 数:void * 返 回:string */ final public function getServerIp() { $serverIp = ''; if(isset($_SERVER['SERVER_ADDR'])) { $serverIp = $_SERVER['SERVER_ADDR']; } elseif(isset($_SERVER['LOCAL_ADDR'])) { $serverIp = $_SERVER['LOCAL_ADDR']; } elseif(isset($_SERVER['HOSTNAME'])) { $serverIp = gethostbyname($_SERVER['HOSTNAME']); } else { $serverIp = getenv('SERVER_ADDR'); } return $serverIp; } /** * 方 法:获取当前URI信息 * 参 数:void * 返 回:string $url */ public function getCurrentUri() { $uri = ''; if($_SERVER ["REMOTE_ADDR"]) {//浏览器浏览模式 $uri = 'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']; } else {//命令行模式 $params = $this->argvs; $uri = $params[0]; array_shift($params); for($i = 0, $len = count($params); $i < $len; $i++) { $uri .= ' ' . $params[$i]; } } return $uri; } /** * 方 法:记录异常信息 * 参 数:errorHandlerException $e 错误异常 * 返 回:boolean 是否保存成功 */ final public function logException($e) { $error = array( 'add_time' => time(), 'title' => errorHandlerException::getName($e->getCode()),//这里获取用户友好型名称 'message' => array(), 'server_ip' => $this->getServerIp(), 'code' => errorHandlerException::getLocalCode($e->getCode()),//这里为各种错误定义一个编号以便查找 'file' => $e->getFile(), 'line' => $e->getLine(), 'url' => $this->getCurrentUri(), ); do { //$e->getFile() . ':' . $e->getLine() . ' ' . $e->getMessage() . '(' . $e->getCode() . ')' $message = (string)$e; $error['message'][] = $message; } while($e = $e->getPrevious()); $error['message'] = implode("\r\n", $error['message']); $this->logError($error); } /** * 方 法:记录异常信息 * 参 数:array $error = array( * 'time' => int, * 'title' => 'string', * 'message' => 'string', * 'code' => int, * 'server_ip' => 'string' * 'file' => 'string', * 'line' => int, * 'url' => 'string', * ); * 返 回:boolean 是否保存成功 */ public function logError($error) { /*这里去实现如何将错误信息记录到日志*/ } }
In the above code, there is an 'errorHandlerException' class, which is placed in the file 'errorHandlerException.class.php'. This class is used to convert errors into exceptions in order to record the file, line number, and error code where the error occurred. , error information and other information, and its method 'isFatalError' is used to identify whether the error is a fatal error. Here we number and name the errors for the convenience of management. The code of this class is as follows
/** * 文件名称:errorHandlerException.class.php * 摘 要:自定义错误异常类 该类继承至PHP内置的错误异常类 */ class errorHandlerException extends ErrorException { public static $localCode = array( E_COMPILE_ERROR => 4001, E_COMPILE_WARNING => 4002, E_CORE_ERROR => 4003, E_CORE_WARNING => 4004, E_DEPRECATED => 4005, E_ERROR => 4006, E_NOTICE => 4007, E_PARSE => 4008, E_RECOVERABLE_ERROR => 4009, E_STRICT => 4010, E_USER_DEPRECATED => 4011, E_USER_ERROR => 4012, E_USER_NOTICE => 4013, E_USER_WARNING => 4014, E_WARNING => 4015, 4016 => 4016, ); public static $localName = array( E_COMPILE_ERROR => 'PHP Compile Error', E_COMPILE_WARNING => 'PHP Compile Warning', E_CORE_ERROR => 'PHP Core Error', E_CORE_WARNING => 'PHP Core Warning', E_DEPRECATED => 'PHP Deprecated Warning', E_ERROR => 'PHP Fatal Error', E_NOTICE => 'PHP Notice', E_PARSE => 'PHP Parse Error', E_RECOVERABLE_ERROR => 'PHP Recoverable Error', E_STRICT => 'PHP Strict Warning', E_USER_DEPRECATED => 'PHP User Deprecated Warning', E_USER_ERROR => 'PHP User Error', E_USER_NOTICE => 'PHP User Notice', E_USER_WARNING => 'PHP User Warning', E_WARNING => 'PHP Warning', 4016 => 'Customer`s Error', ); /** * 方 法:构造函数 * 摘 要:相关知识请查看 http://php.net/manual/en/errorexception.construct.php * * 参 数:string $message 异常信息(可选) * int $code 异常代码(可选) * int $severity * string $filename 异常文件(可选) * int $line 异常的行数(可选) * Exception $previous 上一个异常(可选) * * 返 回:void */ public function __construct($message = '', $code = 0, $severity = 1, $filename = __FILE__, $line = __LINE__, Exception $previous = null) { parent::__construct($message, $code, $severity, $filename, $line, $previous); } /** * 方 法:是否是致命性错误 * 参 数:array $error * 返 回:boolean */ public static function isFatalError($error) { $fatalErrors = array( E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING ); return isset($error['type']) && in_array($error['type'], $fatalErrors); } /** * 方 法:根据原始的错误代码得到本地的错误代码 * 参 数:int $code * 返 回:int $localCode */ public static function getLocalCode($code) { return isset(self::$localCode[$code]) ? self::$localCode[$code] : self::$localCode[4016]; } /** * 方 法:根据原始的错误代码获取用户友好型名称 * 参 数:int * 返 回:string $name */ public static function getName($code) { return isset(self::$localName[$code]) ? self::$localName[$code] : self::$localName[4016]; }
In the error interception class, users need to define their own method for error recording ('logException'). It should be noted here that some errors may occur continuously over a period of time, so they only need to be recorded once. You can use The error code, file, line number, and error details generate an MD5 value to record whether the error has been recorded. If it has been recorded within the specified time (one hour), there is no need to record it again
Then we define a file to instantiate the above class and capture various errors and exceptions. The file is named 'registerErrorHandler.php' and the content is as follows
/* * 使用方法介绍: * 在入口处引入该文件即可,然后可以在该文件中定义调试模式常量'DEBUG_ERROR' * * <?php * * require 'registerErrorHandler.php'; * * ?> */ /** * 调试错误模式: * 0 => 非调试模式,不显示异常、错误信息但记录异常、错误信息 * 1 => 调试模式,显示异常、错误信息但不记录异常、错误信息 */ define('DEBUG_ERROR', 0); require 'errorHandler.class.php'; class registerErrorHandler { /** * 方 法:注册异常、错误拦截 * 参 数:void * 返 回:void */ public static function register() { global $argv; if(DEBUG_ERROR) {//如果开启调试模式 ini_set('display_errors', 1); return; } //如果不开启调试模式 ini_set('error_reporting', -1); ini_set('display_errors', 0); $handler = new errorHandler(); $handler->argvs = $argv;//此处主要兼容命令行模式下获取参数 $handler->register(); } } registerErrorHandler::register();
The rest is that you need to introduce this file in your entry file, define the debugging mode, and then implement your own method of recording errors
It should be noted that some errors that have occurred before you registered and caused the script to interrupt cannot be recorded, because at this time 'registerErrorHandler::register()' has not been executed yet and has been interrupted
Also, the 'set_error_handler' function cannot capture the following types of errors: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING. This can be seen in the official documentation, but it doesn't matter here, because the above errors are parsing and compiling. Error, these have not passed, it is impossible for you to publish it