From 664695553b858998fb79d031c0a7236fde0825b7 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 12 Feb 2016 14:14:55 +0900 Subject: [PATCH] Add default error, exception, and shutdown handlers --- common/autoload.php | 7 +- common/framework/debug.php | 386 +++++++++++++++++++++++++++++++++++++ common/lang/en.php | 2 + common/lang/ja.php | 2 + common/lang/ko.php | 2 + 5 files changed, 398 insertions(+), 1 deletion(-) create mode 100644 common/framework/debug.php diff --git a/common/autoload.php b/common/autoload.php index be9764689..68f7b327f 100644 --- a/common/autoload.php +++ b/common/autoload.php @@ -3,7 +3,7 @@ /** * Set error reporting rules. */ -error_reporting(E_ALL ^ E_WARNING ^ E_NOTICE ^ E_STRICT ^ E_DEPRECATED); +error_reporting(E_ALL ^ E_NOTICE ^ E_STRICT ^ E_DEPRECATED); /** * Suppress date/time errors until the internal time zone is set (see below). @@ -198,6 +198,11 @@ require_once RX_BASEDIR . 'vendor/autoload.php'; */ Rhymix\Framework\Config::init(); +/** + * Install the debugger. + */ +Rhymix\Framework\Debug::registerErrorHandlers(error_reporting()); + /** * Set the internal timezone. */ diff --git a/common/framework/debug.php b/common/framework/debug.php new file mode 100644 index 000000000..0699921e0 --- /dev/null +++ b/common/framework/debug.php @@ -0,0 +1,386 @@ + 'Debug', + 'time' => microtime(true), + 'message' => $message, + 'file' => isset($backtrace[0]['file']) ? $backtrace[0]['file'] : null, + 'line' => isset($backtrace[0]['line']) ? $backtrace[0]['line'] : 0, + 'backtrace' => $backtrace, + ); + self::$_entries[] = $entry; + return true; + } + + /** + * Add a PHP error to the log. + * + * @param int $errno + * @param string $errstr + * @param string $errfile + * @param int $errline + * @param array $errcontext + * @return void + */ + public static function addError($errno, $errstr, $errfile, $errline, $errcontext) + { + // Do not handle error types that we were told to ignore. + if (!($errno & error_reporting())) + { + return; + } + + // Find out the file where the error really occurred. + if (isset(self::$_aliases[$errfile])) + { + $errfile = self::$_aliases[$errfile]; + } + if (!strncmp($errfile, \RX_BASEDIR, strlen(\RX_BASEDIR))) + { + $errfile = substr($errfile, strlen(\RX_BASEDIR)); + } + + // Get the backtrace. + $backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS); + + // Prepare the error entry. + self::$_errors[] = $errinfo = (object)array( + 'type' => self::getErrorType($errno), + 'time' => microtime(), + 'message' => $errstr, + 'file' => $errfile, + 'line' => $errline, + 'backtrace' => $backtrace, + ); + + // Add the entry to the error log. + $log_entry = str_replace("\0", '', sprintf('PHP %s: %s in %s on line %d', + $errinfo->type, $errinfo->message, $errinfo->file, intval($errinfo->line))); + error_log($log_entry); + } + + /** + * Add a query to the log. + * + * @return bool + */ + public static function addQuery() + { + + } + + /** + * Add a slow query to the log. + * + * @return bool + */ + public static function addSlowQuery() + { + + } + + /** + * Add a slow trigger to the log. + * + * @return bool + */ + public static function addSlowTrigger() + { + + } + + /** + * Add a slow widget to the log. + * + * @return bool + */ + public static function addSlowWidget() + { + + } + + /** + * The default handler for catching exceptions. + * + * @param Exception $e + * @return void + */ + public static function exceptionHandler(\Exception $e) + { + // Find out the file where the exception really occurred. + $errfile = $e->getFile(); + if (isset(self::$_aliases[$errfile])) + { + $errfile = self::$_aliases[$errfile]; + } + if (!strncmp($errfile, \RX_BASEDIR, strlen(\RX_BASEDIR))) + { + $errfile = substr($errfile, strlen(\RX_BASEDIR)); + } + + // If the exception was thrown in a Rhymix Framework class, find out where that class was called. + $backtrace = $e->getTrace(); + $caller_errfile = $errfile; + $caller_errline = $e->getLine(); + while (preg_match('#^(classes|common)/#i', $caller_errfile)) + { + $trace = array_shift($backtrace); + if (!$trace) + { + $caller_errfile = $caller_errline = null; + } + else + { + $caller_errfile = $trace['file']; + $caller_errline = $trace['line']; + if (isset(self::$_aliases[$caller_errfile])) + { + $caller_errfile = self::$_aliases[$caller_errfile]; + } + if (!strncmp($caller_errfile, \RX_BASEDIR, strlen(\RX_BASEDIR))) + { + $caller_errfile = substr($caller_errfile, strlen(\RX_BASEDIR)); + } + } + } + + // Add the exception to the error log. + + if ($caller_errfile && $caller_errfile !== $errfile) + { + $log_entry = str_replace("\0", '', sprintf('%s #%d "%s" in %s on line %d (via %s on line %d)', + get_class($e), $e->getCode(), $e->getMessage(), $caller_errfile, $caller_errline, $errfile, $e->getLine())); + } + else + { + $log_entry = str_replace("\0", '', sprintf('%s #%d "%s" in %s on line %d', + get_class($e), $e->getCode(), $e->getMessage(), $errfile, $e->getLine())); + } + error_log('PHP Exception: ' . $log_entry . "\n" . str_replace("\0", '', $e->getTraceAsString())); + + // Display the error screen. + self::displayErrorScreen($log_entry); + } + + /** + * The default handler for catching fatal errors. + * + * @return void + */ + public static function shutdownHandler() + { + // Check if we are exiting because of a fatal error. + $errinfo = error_get_last(); + if ($errinfo === null || ($errinfo['type'] !== 1 && $errinfo['type'] !== 4)) + { + return; + } + + // Find out the file where the fatal error really occurred. + if (isset(self::$_aliases[$errinfo['file']])) + { + $errinfo['file'] = self::$_aliases[$errinfo['file']]; + } + if (!strncmp($errinfo['file'], \RX_BASEDIR, strlen(\RX_BASEDIR))) + { + $errinfo['file'] = substr($errinfo['file'], strlen(\RX_BASEDIR)); + } + + // Add the entry to the error log. + $message = sprintf('%s in %s on line %d', $errinfo['message'], $errinfo['file'], intval($errinfo['line'])); + $log_entry = str_replace("\0", '', 'PHP ' . self::getErrorType($errinfo['type']) . ': ' . $message); + error_log($log_entry); + + // Display the error screen. + self::displayErrorScreen($log_entry); + } + + /** + * Register all error handlers. + * + * @return void + */ + public static function registerErrorHandlers($error_types) + { + set_error_handler('\\Rhymix\\Framework\\Debug::addError', $error_types); + set_exception_handler('\\Rhymix\\Framework\\Debug::exceptionHandler'); + register_shutdown_function('\\Rhymix\\Framework\\Debug::shutdownHandler'); + } + + /** + * Display a fatal error screen. + * + * @param string $message + * @return void + */ + public static function displayErrorScreen($message) + { + // Disable output buffering. + while (ob_get_level()) + { + ob_end_clean(); + } + + // Localize the error title. + $title = \Context::getLang('msg_server_error'); + if ($title === 'msg_server_error') + { + $message = 'Server Error'; + } + + // Localize the error message. + $message = ini_get('display_errors') ? $message : \Context::getLang('msg_server_error_see_log'); + if ($message === 'msg_server_error_see_log') + { + $message = 'Your server is configured to hide error messages. Please see your server\'s error log for details.'; + } + + // Display a generic error page. + \Context::displayErrorPage($title, $message, 500); + exit; + } + + /** + * Convert a PHP error number to the corresponding error name. + * + * @param int $errno + * @return string + */ + public static function getErrorType($errno) + { + switch ($errno) + { + case \E_ERROR: return 'Fatal Error'; + case \E_WARNING: return 'Warning'; + case \E_NOTICE: return 'Notice'; + case \E_CORE_ERROR: return 'Core Error'; + case \E_CORE_WARNING: return 'Core Warning'; + case \E_COMPILE_ERROR: return 'Compile-time Error'; + case \E_COMPILE_WARNING: return 'Compile-time Warning'; + case \E_USER_ERROR: return 'User Error'; + case \E_USER_WARNING: return 'User Warning'; + case \E_USER_NOTICE: return 'User Notice'; + case \E_STRICT: return 'Strict Standards'; + case \E_PARSE: return 'Parse Error'; + case \E_DEPRECATED: return 'Deprecated'; + case \E_USER_DEPRECATED: return 'User Deprecated'; + case \E_RECOVERABLE_ERROR: return 'Catchable Fatal Error'; + default: return 'Error'; + } + } +} diff --git a/common/lang/en.php b/common/lang/en.php index a6f127672..9e40662e0 100644 --- a/common/lang/en.php +++ b/common/lang/en.php @@ -225,6 +225,8 @@ $lang->msg_module_is_not_exists = 'Cannot find the page you requested. Ask your $lang->msg_module_is_not_standalone = 'Requested page cannot be executed independently.'; $lang->msg_empty_search_target = 'Cannot find the Search target.'; $lang->msg_empty_search_keyword = 'Cannot find the Keyword.'; +$lang->msg_server_error = 'Server Error'; +$lang->msg_server_error_see_log = 'Your server is configured to hide error messages. Please see your server\'s error log for details.'; $lang->comment_to_be_approved = 'Your comment must be approved by admin before being published.'; $lang->success_registed = 'Registered successfully.'; $lang->success_declared = 'Reported successfully.'; diff --git a/common/lang/ja.php b/common/lang/ja.php index 55a97b1a6..789706a53 100644 --- a/common/lang/ja.php +++ b/common/lang/ja.php @@ -225,6 +225,8 @@ $lang->msg_module_is_not_exists = 'モジュールが見つかりません。 $lang->msg_module_is_not_standalone = 'このモジュールはスタンドアローンでは作動しません。'; $lang->msg_empty_search_target = '検索対象がありません。'; $lang->msg_empty_search_keyword = 'キーワードがありません。'; +$lang->msg_server_error = 'サーバーエラー'; +$lang->msg_server_error_see_log = 'エラーメッセージを表示しないように設定されています。サーバーのエラーログで詳細を確認してください。'; $lang->comment_to_be_approved = '管理者の確認が必要なコメントです。'; $lang->success_registed = '登録しました。'; $lang->success_declared = '通報しました。'; diff --git a/common/lang/ko.php b/common/lang/ko.php index 612b63536..69835c2fd 100644 --- a/common/lang/ko.php +++ b/common/lang/ko.php @@ -225,6 +225,8 @@ $lang->msg_module_is_not_exists = '요청한 페이지를 찾을 수 없습니 $lang->msg_module_is_not_standalone = '요청한 페이지는 독립적으로 동작할 수 없습니다.'; $lang->msg_empty_search_target = '검색대상이 없습니다.'; $lang->msg_empty_search_keyword = '검색어가 없습니다.'; +$lang->msg_server_error = '서버 오류'; +$lang->msg_server_error_see_log = '오류 메시지를 표시하지 않도록 설정되어 있습니다. 서버의 에러 로그에서 자세한 내용을 확인해 주십시오.'; $lang->comment_to_be_approved = '관리자의 확인이 필요한 댓글입니다.'; $lang->success_registed = '등록했습니다.'; $lang->success_declared = '신고했습니다.';