zoukankan      html  css  js  c++  java
  • 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))). "
    ";
          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 />';
        }
      }
    }
  • 相关阅读:
    Codeforces Round #260 (Div. 2)
    面试题:给定数组a,找到最大的j-i, 使a[j]>a[i]
    ssh自动输入密码脚本 切换目录脚本
    make工作时的执行步骤
    Codeforces Round #259 (Div. 2)
    Codeforces Round #258 (Div. 2)
    如何在半径为1的圆中随机选取一个点
    面试中常问的有关随机选取k个数的总结
    topcoder SRM 628 DIV2 BracketExpressions
    topcoder SRM 628 DIV2 BishopMove
  • 原文地址:https://www.cnblogs.com/eastson/p/3360751.html
Copyright © 2011-2022 走看看