From c19d23c248a2bba342a83866be989c003f84ee72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BA=A6=E5=BD=93=E8=8B=97=E5=84=BF?= Date: Mon, 1 Feb 2016 01:24:02 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84Error=E7=B1=BB=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E5=BC=82=E5=B8=B8=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/think/Error.php | 338 ++++++++++++++++++++-------- tpl/think_exception.tpl | 471 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 672 insertions(+), 137 deletions(-) diff --git a/library/think/Error.php b/library/think/Error.php index 11024459..370c1bb1 100644 --- a/library/think/Error.php +++ b/library/think/Error.php @@ -6,129 +6,279 @@ // +---------------------------------------------------------------------- // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) // +---------------------------------------------------------------------- -// | Author: liu21st +// | Author: 麦当苗儿 // +---------------------------------------------------------------------- namespace think; +use think\Exception; +use think\exception\ErrorException; +use think\exception\NotFoundException; + class Error { /** - * 自定义异常处理 - * @access public - * @param mixed $e 异常对象 + * 注册异常处理 + * @return null */ - public static function appException($e) + public static function register() { - $error = [ - 'message' => $e->getMessage(), - 'file' => $e->getFile(), - 'line' => $e->getLine(), - 'trace' => $e->getTraceAsString(), - 'code' => $e->getCode(), - ]; - // 发送http状态信息 - http_response_code(Config::get('exception_http_status')); - // 输出异常页面 - self::halt($error); + // Workaround PHP bug 42098 + // https://bugs.php.net/bug.php?id=42098 + // class_exists(think\exception\ErrorException::class); + + set_error_handler([__CLASS__, 'appError']); + set_exception_handler([__CLASS__, 'appException']); + register_shutdown_function([__CLASS__, 'appShutdown']); } /** - * 自定义错误处理 - * @access public - * @param int $error_number 错误类型 - * @param string $error_string 错误信息 - * @param string $error_file 错误文件 - * @param int $error_line 错误行数 - * @return void + * Exception Handler + * @param \Exception $exception + * @return Boolean true 禁止往下传播已处理过的异常 */ - public static function appError($error_number, $error_string, $error_file, $error_line) + public static function appException(\Exception $exception) { - $errorStr = "[{$error_number}] {$error_string} {$error_file} 第 {$error_line} 行."; - switch ($error_number) { - case E_USER_ERROR: - self::halt($errorStr, $error_number); - break; - case E_STRICT: - case E_USER_WARNING: - case E_USER_NOTICE: - default: - Log::record($errorStr, 'notic'); - break; + /* 非API模式下的部署模式,跳转到指定的 Error Page */ + if(!(APP_DEBUG || IS_API)){ + $error_page = Config::get('error_page'); + if (!empty($error_page)) { + header("Location: {$error_page}"); + } } + + /* 收集异常数据 */ + if(APP_DEBUG){ + /* 调试模式,获取详细的错误信息 */ + $data = [ + 'name' => get_class($exception), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'message' => $exception->getMessage(), + 'trace' => $exception->getTrace(), + 'code' => self::getCode($exception), + 'source' => self::getSourceCode($exception), + 'datas' => self::getExtendData($exception), + + 'tables' => [ + 'GET Data' => $_GET, + 'POST Data' => $_POST, + 'Files' => $_FILES, + 'Cookies' => $_COOKIE, + 'Session' => isset($_SESSION) ? $_SESSION : [], + 'Server/Request Data' => $_SERVER, + 'Environment Variables' => $_ENV, + 'ThinkPHP Constants' => self::getTPConst(), + ] + ]; + } else { + /* 部署模式仅显示 Code 和 Message */ + $data = [ + 'code' => $exception->getCode(), + 'message' => Config::get('show_error_msg') ? $exception->getMessage() : Config::get('error_message') + ]; + } + + /* 输出错误信息 */ + self::output($exception, $data); + + // 禁止往下传播已处理过的异常 + return true; } /** - * 应用关闭处理 - * @return void + * Error Handler + * @param integer $errno 错误编号 + * @param integer $errstr 详细错误信息 + * @param string $errfile 出错的文件 + * @param integer $errline 出错行号 + * @return boolean true 禁止往下传播已处理过的异常 + */ + public static function appError($errno, $errstr, $errfile, $errline) + { + // 将错误信息托管至 think\exception\ErrorException + throw new ErrorException($errno, $errstr, $errfile, $errline); + + // 禁止往下传播已处理过的异常 + return true; + } + + /** + * Shutdown Handler + * @return boolean true-禁止往下传播已处理过的异常; false-未处理的异常继续传播 */ public static function appShutdown() { - // 记录日志 - Log::save(); - if ($e = error_get_last()) { - switch ($e['type']) { - case E_ERROR: - case E_PARSE: - case E_CORE_ERROR: - case E_COMPILE_ERROR: - case E_USER_ERROR: - ob_end_clean(); - self::halt($e); - break; - } + if($error = error_get_last()){ + // 将错误信息托管至think\ErrorException + $exception = new ErrorException( + $error['type'], + $error['message'], + $error['file'], + $error['line'] + ); + + /** + * Shutdown handler 中的异常将不被往下传播 + * 所以,这里我们必须手动传播而不能像 Error handler 中那样 throw + */ + self::appException($exception); + + // 禁止往下传播已处理过的异常 + return true; + } + + return false; + } + + /** + * 输出异常信息 + * @param \Exception $exception + * @param Array $vars 异常信息 + * @return null + */ + public static function output(\Exception $exception, Array $vars) + { + if($exception instanceof Exception){ + http_response_code($exception->getHttpStatus()); + } else { + http_response_code(500); + } + + // header('Content-Type: application/json'); + // echo json_encode($vars);exit; + + $type = Config::get('default_return_type'); + if (IS_API && 'html' != $type) { + // 异常信息输出监听 + APP_HOOK && Hook::listen('error_output', $data); + // 输出异常内容 + Response::send($data, $type, Config::get('response_return')); + } else { + ob_end_clean(); + extract($vars); + include Config::get('exception_tmpl'); } } /** - * 错误输出 - * - * @param mixed $error 错误 - * @param int $code + * 获取错误编码 + * ErrorException则使用错误级别作为错误编码 + * @param \Exception $exception + * @return integer 错误编码 */ - public static function halt($error, $code = 1) + private static function getCode(\Exception $exception) { - $message = is_array($error) ? $error['message'] : $error; - $code = isset($error['code']) ? $error['code'] : $code; + $code = $exception->getCode(); + + if(!$code && $exception instanceof ErrorException){ + $code = $exception->getSeverity(); + } - if (APP_DEBUG) { - //调试模式下输出错误信息 - if (!is_array($error)) { - $trace = debug_backtrace(); - $e['message'] = $error; - $e['code'] = $code; - $e['file'] = $trace[0]['file']; - $e['line'] = $trace[0]['line']; - ob_start(); - debug_print_backtrace(); - $e['trace'] = ob_get_clean(); - } else { - $e = $error; - } - } elseif (!IS_API) { - //否则定向到错误页面 - $error_page = Config::get('error_page'); - if (!empty($error_page)) { - header('Location: ' . $error_page); - } else { - $e['code'] = $code; - $e['message'] = Config::get('show_error_msg') ? $message : Config::get('error_message'); - } - } else { - $e = ['message' => $message, 'code' => $code]; - } - // 记录异常日志 - Log::write('[' . $e['code'] . '] ' . $e['message'] . '[' . $e['file'] . ' : ' . $e['line'] . ']', 'error'); + return $code; + } - $type = Config::get('default_return_type'); - if (!IS_API && 'html' == $type) { - include Config::get('exception_tmpl'); - } else { - // 异常信息输出监听 - APP_HOOK && Hook::listen('error_output', $e); - // 输出异常内容 - Response::send($e, $type, Config::get('response_return')); + /** + * 获取出错文件内容 + * 获取错误的前9行和后9行 + * @param \Exception $exception + * @return array 错误文件内容 + */ + private static function getSourceCode(\Exception $exception) + { + // 读取前9行和后9行 + $line = $exception->getLine(); + $first = ($line - 9 > 0) ? $line - 9 : 1; + + try { + $contents = file($exception->getFile()); + + $source = [ + 'first' => $first, + 'source' => array_slice($contents, $first - 1, 19) + ]; + } catch (Exception $e){ + $source = []; } - exit; + + return $source; + } + + /** + * 获取异常扩展信息 + * 用于非调试模式html返回类型显示 + * @param \Exception $exception + * @return array 异常类定义的扩展数据 + */ + private static function getExtendData(\Exception $exception) + { + $data = []; + + if($exception instanceof Exception){ + $data = $exception->getData(); + } + + return $data; + } + + /** + * 获取ThinkPHP常量列表 + * @return array 常量列表 + */ + private static function getTPConst() + { + return [ + 'THINK_VERSION' => defined('THINK_VERSION') ? THINK_VERSION : 'undefined', + 'THINK_PATH' => defined('THINK_PATH') ? THINK_PATH : 'undefined', + 'LIB_PATH' => defined('LIB_PATH') ? LIB_PATH : 'undefined', + 'EXTEND_PATH' => defined('EXTEND_PATH') ? EXTEND_PATH : 'undefined', + 'MODE_PATH' => defined('MODE_PATH') ? MODE_PATH : 'undefined', + 'CORE_PATH' => defined('CORE_PATH') ? CORE_PATH : 'undefined', + 'ORG_PATH' => defined('ORG_PATH') ? ORG_PATH : 'undefined', + 'TRAIT_PATH' => defined('TRAIT_PATH') ? TRAIT_PATH : 'undefined', + 'APP_PATH' => defined('APP_PATH') ? APP_PATH : 'undefined', + 'RUNTIME_PATH' => defined('RUNTIME_PATH') ? RUNTIME_PATH : 'undefined', + 'DATA_PATH' => defined('DATA_PATH') ? DATA_PATH : 'undefined', + 'LOG_PATH' => defined('LOG_PATH') ? LOG_PATH : 'undefined', + 'CACHE_PATH' => defined('CACHE_PATH') ? CACHE_PATH : 'undefined', + 'TEMP_PATH' => defined('TEMP_PATH') ? TEMP_PATH : 'undefined', + 'VENDOR_PATH' => defined('VENDOR_PATH') ? VENDOR_PATH : 'undefined', + 'MODULE_PATH' => defined('MODULE_PATH') ? MODULE_PATH : 'undefined', + 'VIEW_PATH' => defined('VIEW_PATH') ? VIEW_PATH : 'undefined', + 'APP_NAMESPACE' => defined('APP_NAMESPACE') ? APP_NAMESPACE : 'undefined', + 'COMMON_MODULE' => defined('COMMON_MODULE') ? COMMON_MODULE : 'undefined', + 'APP_MULTI_MODULE' => defined('APP_MULTI_MODULE') ? APP_MULTI_MODULE : 'undefined', + 'MODULE_ALIAS' => defined('MODULE_ALIAS') ? MODULE_ALIAS : 'undefined', + 'MODULE_NAME' => defined('MODULE_NAME') ? MODULE_NAME : 'undefined', + 'CONTROLLER_NAME' => defined('CONTROLLER_NAME') ? CONTROLLER_NAME : 'undefined', + 'ACTION_NAME' => defined('ACTION_NAME') ? ACTION_NAME : 'undefined', + 'MODEL_LAYER' => defined('MODEL_LAYER') ? MODEL_LAYER : 'undefined', + 'VIEW_LAYER' => defined('VIEW_LAYER') ? VIEW_LAYER : 'undefined', + 'CONTROLLER_LAYER' => defined('CONTROLLER_LAYER') ? CONTROLLER_LAYER : 'undefined', + 'APP_DEBUG' => defined('APP_DEBUG') ? APP_DEBUG : 'undefined', + 'APP_HOOK' => defined('APP_HOOK') ? APP_HOOK : 'undefined', + 'ENV_PREFIX' => defined('ENV_PREFIX') ? ENV_PREFIX : 'undefined', + 'IS_API' => defined('IS_API') ? IS_API : 'undefined', + 'APP_AUTO_BUILD' => defined('APP_AUTO_BUILD') ? APP_AUTO_BUILD : 'undefined', + 'APP_AUTO_RUN' => defined('APP_AUTO_RUN') ? APP_AUTO_RUN : 'undefined', + 'APP_MODE' => defined('APP_MODE') ? APP_MODE : 'undefined', + 'REQUEST_METHOD' => defined('REQUEST_METHOD') ? REQUEST_METHOD : 'undefined', + 'IS_CGI' => defined('IS_CGI') ? IS_CGI : 'undefined', + 'IS_WIN' => defined('IS_WIN') ? IS_WIN : 'undefined', + 'IS_CLI' => defined('IS_CLI') ? IS_CLI : 'undefined', + 'IS_AJAX' => defined('IS_AJAX') ? IS_AJAX : 'undefined', + 'IS_GET' => defined('IS_GET') ? IS_GET : 'undefined', + 'IS_POST' => defined('IS_POST') ? IS_POST : 'undefined', + 'IS_PUT' => defined('IS_PUT') ? IS_PUT : 'undefined', + 'IS_DELETE' => defined('IS_DELETE') ? IS_DELETE : 'undefined', + 'NOW_TIME' => defined('NOW_TIME') ? NOW_TIME : 'undefined', + 'LANG_SET' => defined('LANG_SET') ? LANG_SET : 'undefined', + 'EXT' => defined('EXT') ? EXT : 'undefined', + 'DS' => defined('DS') ? DS : 'undefined', + '__INFO__' => defined('__INFO__') ? __INFO__ : 'undefined', + '__EXT__' => defined('__EXT__') ? __EXT__ : 'undefined', + '__INFO__' => defined('__INFO__') ? __INFO__ : 'undefined', + '__EXT__' => defined('__EXT__') ? __EXT__ : 'undefined', + ]; } } diff --git a/tpl/think_exception.tpl b/tpl/think_exception.tpl index 31b77249..f63e5fad 100644 --- a/tpl/think_exception.tpl +++ b/tpl/think_exception.tpl @@ -1,53 +1,438 @@ - - - -系统发生错误 - + + + + + 系统发生错误 + + -
-

:(

-

[ '.$e['code'].' ] ';} echo strip_tags($e['message']);?>

-
- +
+

+
-
-

错误位置

-
-
-

FILE:  LINE:

+
+
+
- - -
-
-

追溯信息

-
-
-

-
-
- +

+
+
    $value) { ?>
+
+
+

Call Stack

+
    +
  1. + +
  2. + +
  3. + +
-