Drupal在配置阶段的最开始就设置了自己的错误处理器和异常处理器:
function _drupal_bootstrap_configuration() { set_error_handler('_drupal_error_handler'); set_exception_handler('_drupal_exception_handler'); // ... ... }
先来看看错误处理器_drupal_error_handler()是如何做的?
function _drupal_error_handler($error_level, $message, $filename, $line, $context) { require_once DRUPAL_ROOT . '/includes/errors.inc'; _drupal_error_handler_real($error_level, $message, $filename, $line, $context); }
调用了_drupal_error_handler_real()函数,该函数与当前error_reporting()比较,判断当前错误是否需要被处理:
function _drupal_error_handler_real($error_level, $message, $filename, $line, $context) { if ($error_level & error_reporting()) { $types = drupal_error_levels(); list($severity_msg, $severity_level) = $types[$error_level]; $caller = _drupal_get_last_caller(debug_backtrace()); if (!function_exists('filter_xss_admin')) { require_once DRUPAL_ROOT . '/includes/common.inc'; } // We treat recoverable errors as fatal. _drupal_log_error(array( '%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error', // The standard PHP error handler considers that the error messages // are HTML. We mimick this behavior here. '!message' => filter_xss_admin($message), '%function' => $caller['function'], '%file' => $caller['file'], '%line' => $caller['line'], 'severity_level' => $severity_level, ), $error_level == E_RECOVERABLE_ERROR); } }
重点是_drupal_log_error()函数,该函数具体地处理错误逻辑:
function _drupal_log_error($error, $fatal = FALSE) { // Initialize a maintenance theme if the bootstrap was not complete. // Do it early because drupal_set_message() triggers a drupal_theme_initialize(). if ($fatal && (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL)) { unset($GLOBALS['theme']); if (!defined('MAINTENANCE_MODE')) { define('MAINTENANCE_MODE', 'error'); } drupal_maintenance_theme(); } // When running inside the testing framework, we relay the errors // to the tested site by the way of HTTP headers. $test_info = &$GLOBALS['drupal_test_info']; if (!empty($test_info['in_child_site']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) { // $number does not use drupal_static as it should not be reset // as it uniquely identifies each PHP error. static $number = 0; $assertion = array( $error['!message'], $error['%type'], array( 'function' => $error['%function'], 'file' => $error['%file'], 'line' => $error['%line'], ), ); header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion))); $number++; } watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']); if ($fatal) { drupal_add_http_header('Status', '500 Service unavailable (with message)'); } if (drupal_is_cli()) { if ($fatal) { // When called from CLI, simply output a plain text message. print html_entity_decode(strip_tags(t('%type: !message in %function (line %line of %file).', $error))). " "; exit; } } if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') { if ($fatal) { if (error_displayable($error)) { // When called from JavaScript, simply output the error message. print t('%type: !message in %function (line %line of %file).', $error); } exit; } } else { // Display the message if the current error reporting level allows this type // of message to be displayed, and unconditionnaly in update.php. if (error_displayable($error)) { $class = 'error'; // If error type is 'User notice' then treat it as debug information // instead of an error message, see dd(). if ($error['%type'] == 'User notice') { $error['%type'] = 'Debug'; $class = 'status'; } drupal_set_message(t('%type: !message in %function (line %line of %file).', $error), $class); } if ($fatal) { drupal_set_title(t('Error')); // We fallback to a maintenance page at this point, because the page generation // itself can generate errors. print theme('maintenance_page', array('content' => t('The website encountered an unexpected error. Please try again later.'))); exit; } } }
有两点需要先说一下:
1. 什么是严重错误$fatal?$error_level == E_RECOVERABLE_ERROR,还有异常也是严重错误。
2. 哪些错误可以显示error_displayable($error)==TRUE?这是在Drupal系统配置中设置的:
在_drupal_log_error()函数中,首先检查是否是在启动过程中发生的严重地不可恢复错误,如果是不可恢复的错误,则切换到维护显示主题:
if ($fatal && (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL)) { unset($GLOBALS['theme']); if (!defined('MAINTENANCE_MODE')) { define('MAINTENANCE_MODE', 'error'); } drupal_maintenance_theme(); }
检查是否运行在SimpleTest框架,如果是,则需要为SimpleTest返回一个包含错误信息的HTTP头:
$test_info = &$GLOBALS['drupal_test_info']; if (!empty($test_info['in_child_site']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) { static $number = 0; $assertion = array( $error['!message'], $error['%type'], array( 'function' => $error['%function'], 'file' => $error['%file'], 'line' => $error['%line'], ), ); header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion))); $number++; }
然后调用watchdog()记录错误信息。watchdog()使用了钩子,这样允许开发人员自定义模块将错误信息记录到各种不同的地方。默认的Drupal使用dblog模块将错误信息保存到后台数据库watchdog表中。
watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']);
function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) {
global $user, $base_root;
static $in_error_state = FALSE;
// It is possible that the error handling will itself trigger an error. In that case, we could
// end up in an infinite loop. To avoid that, we implement a simple static semaphore.
if (!$in_error_state && function_exists('module_implements')) {
$in_error_state = TRUE;
// The user object may not exist in all conditions, so 0 is substituted if needed.
$user_uid = isset($user->uid) ? $user->uid : 0;
// Prepare the fields to be logged
$log_entry = array(
'type' => $type,
'message' => $message,
'variables' => $variables,
'severity' => $severity,
'link' => $link,
'user' => $user,
'uid' => $user_uid,
'request_uri' => $base_root . request_uri(),
'referer' => isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '',
'ip' => ip_address(),
// Request time isn't accurate for long processes, use time() instead.
'timestamp' => time(),
);
// Call the logging hooks to log/process the message
foreach (module_implements('watchdog') as $module) {
module_invoke($module, 'watchdog', $log_entry);
}
// It is critical that the semaphore is only cleared here, in the parent
// watchdog() call (not outside the loop), to prevent recursive execution.
$in_error_state = FALSE;
}
}
// dblog模块
function dblog_watchdog(array $log_entry) {
Database::getConnection('default', 'default')->insert('watchdog')
->fields(array(
'uid' => $log_entry['uid'],
'type' => substr($log_entry['type'], 0, 64),
'message' => $log_entry['message'],
'variables' => serialize($log_entry['variables']),
'severity' => $log_entry['severity'],
'link' => substr($log_entry['link'], 0, 255),
'location' => $log_entry['request_uri'],
'referer' => $log_entry['referer'],
'hostname' => substr($log_entry['ip'], 0, 128),
'timestamp' => $log_entry['timestamp'],
))
->execute();
}
如果是严重错误,Drupal会返回500状态码:
if ($fatal) { drupal_add_http_header('Status', '500 Service unavailable (with message)'); }
检查是否是AJAX请求。$_SERVER['HTTP_X_REQUESTED_WITH']等于XMLHttpRequest是AJAX请求。如果是AJAX请求,且错误允许显示(error_displayable($error)为TRUE),则显示错误信息,终止请求。
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') { if ($fatal) { if (error_displayable($error)) { // When called from JavaScript, simply output the error message. print t('%type: !message in %function (line %line of %file).', $error); } exit; } }
如果不是AJAX请求,且错误允许显示,则加入一条FLASH信息,这条信息会显示在下一次请求的顶部。
if (error_displayable($error)) { $class = 'error'; // If error type is 'User notice' then treat it as debug information // instead of an error message, see dd(). if ($error['%type'] == 'User notice') { $error['%type'] = 'Debug'; $class = 'status'; } drupal_set_message(t('%type: !message in %function (line %line of %file).', $error), $class); }
最后,对于非AJAX的严重错误,直接输出信息,终止请求。
if ($fatal) { drupal_set_title(t('Error')); // We fallback to a maintenance page at this point, because the page generation // itself can generate errors. print theme('maintenance_page', array('content' => t('The website encountered an unexpected error. Please try again later.'))); exit; }
异常处理器和错误处理器实质上是相同的,内部都是通过_drupal_log_error()来处理的:
function _drupal_exception_handler($exception) { require_once DRUPAL_ROOT . '/includes/errors.inc'; try { // Log the message to the watchdog and return an error page to the user. _drupal_log_error(_drupal_decode_exception($exception), TRUE); } catch (Exception $exception2) { // Another uncaught exception was thrown while handling the first one. // If we are displaying errors, then do so with no possibility of a further uncaught exception being thrown. if (error_displayable()) { print '<h1>Additional uncaught exception thrown while handling exception.</h1>'; print '<h2>Original</h2><p>' . _drupal_render_exception_safe($exception) . '</p>'; print '<h2>Additional</h2><p>' . _drupal_render_exception_safe($exception2) . '</p><hr />'; } } }