Drupal的错误和异常处理

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))). "\n";

      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的错误和异常处理

 

在_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 />';

    }

  }

}

你可能感兴趣的:(drupal)