diff --git a/includes/errors.inc b/includes/errors.inc index 9d0df05..3d722dc 100644 --- a/includes/errors.inc +++ b/includes/errors.inc @@ -21,6 +21,11 @@ define('ERROR_REPORTING_DISPLAY_SOME', 1); define('ERROR_REPORTING_DISPLAY_ALL', 2); /** + * Error reporting level: display all messages, plus backtrace information. + */ +define('ERROR_REPORTING_DISPLAY_VERBOSE', 3); + +/** * Maps PHP error constants to watchdog severity levels. * * The error constants are documented at @@ -71,7 +76,8 @@ function _drupal_error_handler_real($error_level, $message, $filename, $line, $c if ($error_level & error_reporting()) { $types = drupal_error_levels(); list($severity_msg, $severity_level) = $types[$error_level]; - $caller = _drupal_get_last_caller(debug_backtrace()); + $backtrace = debug_backtrace(); + $caller = _drupal_get_last_caller($backtrace); if (!function_exists('filter_xss_admin')) { require_once DRUPAL_ROOT . '/includes/common.inc'; @@ -87,6 +93,7 @@ function _drupal_error_handler_real($error_level, $message, $filename, $line, $c '%file' => $caller['file'], '%line' => $caller['line'], 'severity_level' => $severity_level, + 'backtrace' => $backtrace, ), $error_level == E_RECOVERABLE_ERROR); } } @@ -166,7 +173,8 @@ function _drupal_render_exception_safe($exception) { function error_displayable($error = NULL) { $error_level = variable_get('error_level', ERROR_REPORTING_DISPLAY_ALL); $updating = (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update'); - $all_errors_displayed = ($error_level == ERROR_REPORTING_DISPLAY_ALL); + $all_errors_displayed = ($error_level == ERROR_REPORTING_DISPLAY_ALL) || + ($error_level == ERROR_REPORTING_DISPLAY_VERBOSE); $error_needs_display = ($error_level == ERROR_REPORTING_DISPLAY_SOME && isset($error) && $error['%type'] != 'Notice' && $error['%type'] != 'Strict warning'); @@ -177,9 +185,10 @@ function error_displayable($error = NULL) { * Logs a PHP error or exception and displays an error page in fatal cases. * * @param $error - * An array with the following keys: %type, !message, %function, %file, %line - * and severity_level. All the parameters are plain-text, with the exception - * of !message, which needs to be a safe HTML string. + * An array with the following keys: %type, !message, %function, %file, + * %line, severity_level, and backtrace. All the parameters are plain-text, + * with the exception of !message, which needs to be a safe HTML string, and + * backtrace, which is a standard PHP backtrace. * @param $fatal * TRUE if the error is fatal. */ @@ -194,6 +203,10 @@ function _drupal_log_error($error, $fatal = FALSE) { drupal_maintenance_theme(); } + // Backtrace array is not a valid replacement value for t(). + $backtrace = $error['backtrace']; + unset($error['backtrace']); + // 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']; @@ -244,13 +257,34 @@ function _drupal_log_error($error, $fatal = FALSE) { $class = 'error'; // If error type is 'User notice' then treat it as debug information - // instead of an error message, see dd(). + // instead of an error message. + // @see debug() if ($error['%type'] == 'User notice') { $error['%type'] = 'Debug'; $class = 'status'; } - drupal_set_message(t('%type: !message in %function (line %line of %file).', $error), $class); + // Attempt to reduce verbosity by removing DRUPAL_ROOT from the file path + // in the message. This does not happen for (false) security. + $root_length = strlen(DRUPAL_ROOT); + if (substr($error['%file'], 0, $root_length) == DRUPAL_ROOT) { + $error['%file'] = substr($error['%file'], $root_length + 1); + } + $message = t('%type: !message in %function (line %line of %file).', $error); + + // Check if verbose error reporting is on. + $error_level = variable_get('error_level', ERROR_REPORTING_DISPLAY_ALL); + + if ($error_level == ERROR_REPORTING_DISPLAY_VERBOSE) { + // First trace is the error itself, already contained in the message. + // While the second trace is the error source and also contained in the + // message, the message doesn't contain argument values, so we output it + // once more in the backtrace. + array_shift($backtrace); + // Generate a backtrace containing only scalar argument values. + $message .= '
' . format_backtrace($backtrace) . '
'; + } + drupal_set_message($message, $class); } if ($fatal) { @@ -267,12 +301,12 @@ function _drupal_log_error($error, $fatal = FALSE) { * Gets the last caller from a backtrace. * * @param $backtrace - * A standard PHP backtrace. + * A standard PHP backtrace. Passed by reference. * * @return * An associative array with keys 'file', 'line' and 'function'. */ -function _drupal_get_last_caller($backtrace) { +function _drupal_get_last_caller(&$backtrace) { // Errors that occur inside PHP internal functions do not generate // information about file and line. Ignore black listed functions. $blacklist = array('debug', '_drupal_error_handler', '_drupal_exception_handler'); @@ -299,3 +333,40 @@ function _drupal_get_last_caller($backtrace) { } return $call; } + +/** + * Formats a backtrace into a plain-text string. + * + * The calls show values for scalar arguments and type names for complex ones. + * + * @param array $backtrace + * A standard PHP backtrace. + * + * @return string + * A plain-text line-wrapped string ready to be put inside
.
+ */
+function format_backtrace(array $backtrace) {
+  $return = '';
+  foreach ($backtrace as $trace) {
+    $call = array('function' => '', 'args' => array());
+    if (isset($trace['class'])) {
+      $call['function'] = $trace['class'] . $trace['type'] . $trace['function'];
+    }
+    elseif (isset($trace['function'])) {
+      $call['function'] = $trace['function'];
+    }
+    else {
+      $call['function'] = 'main';
+    }
+    foreach ($trace['args'] as $arg) {
+      if (is_scalar($arg)) {
+        $call['args'][] = is_string($arg) ? '\'' . filter_xss($arg) . '\'' : $arg;
+      }
+      else {
+        $call['args'][] = ucfirst(gettype($arg));
+      }
+    }
+    $return .= $call['function'] . '(' . implode(', ', $call['args']) . ")\n";
+  }
+  return $return;
+}
diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc
index 23a975b..3eed478 100644
--- a/modules/system/system.admin.inc
+++ b/modules/system/system.admin.inc
@@ -1646,6 +1646,7 @@ function system_logging_settings() {
       ERROR_REPORTING_HIDE => t('None'),
       ERROR_REPORTING_DISPLAY_SOME => t('Errors and warnings'),
       ERROR_REPORTING_DISPLAY_ALL => t('All messages'),
+      ERROR_REPORTING_DISPLAY_VERBOSE => t('All messages, with backtrace information'),
     ),
     '#description' => t('It is recommended that sites running on production environments do not display any errors.'),
   );