diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index da3a277..17d9521 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -64,9 +64,40 @@ const ERROR_REPORTING_DISPLAY_ALL = 'all'; /** - * Error reporting level: display all messages, plus backtrace information. + * Error reporting: Add stacktrace information to logs. + * + * Note that ErrorHandlerTest() relies on the following to be numbered + * sequentially so if that changes for any reason revise the function. + * + */ +const ERROR_REPORTING_DISPLAY_LOG_EXTRA = 1; // Do not start at zero. + +/** + * Error reporting: Add stacktrace information to messages on page. + */ +const ERROR_REPORTING_DISPLAY_OUTPUT_EXTRA = 2; + +/** + * Error reporting: Include passed parameter types. + */ +const ERROR_REPORTING_DISPLAY_PASSED_PARAMS = 3; + +/** + * Error reporting: If ERROR_REPORTING_DISPLAY_PASSED_PARAMS + * then this setting will show data in the scalars. + */ +const ERROR_REPORTING_DISPLAY_PASSED_SCALARS = 4; + +/** + * Error reporting: Args can be comma or line separated. + */ +const ERROR_REPORTING_DISPLAY_LINE_PER_ARG = 5; + +/** + * Error reporting: If ERROR_REPORTING_DISPLAY_PASSED_SCALARS + * then this setting will shorten string with DRUPAL_ROOT substrings. */ -const ERROR_REPORTING_DISPLAY_VERBOSE = 'verbose'; +const ERROR_REPORTING_DISPLAY_STRIP_DRUPAL_ROOT_STRING_ARGS = 6; /** * @defgroup logging_severity_levels Logging severity levels diff --git a/core/includes/errors.inc b/core/includes/errors.inc index 3fe23b7..9413745 100644 --- a/core/includes/errors.inc +++ b/core/includes/errors.inc @@ -56,7 +56,7 @@ 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]; - $backtrace = debug_backtrace(); + $backtrace = debug_backtrace(TRUE); $caller = _drupal_get_last_caller($backtrace); if (!function_exists('filter_xss_admin')) { @@ -73,7 +73,7 @@ function _drupal_error_handler_real($error_level, $message, $filename, $line, $c '%file' => $caller['file'], '%line' => $caller['line'], 'severity_level' => $severity_level, - 'backtrace' => $backtrace, + '!backtrace' => $backtrace, ), $error_level == E_RECOVERABLE_ERROR); } } @@ -118,11 +118,11 @@ function _drupal_decode_exception($exception) { // The standard PHP exception handler considers that the exception message // is plain-text. We mimick this behavior here. '!message' => check_plain($message), + '!backtrace' => $backtrace, '%function' => $caller['function'], '%file' => $caller['file'], '%line' => $caller['line'], 'severity_level' => WATCHDOG_ERROR, - 'backtrace' => $backtrace, ); } @@ -150,6 +150,10 @@ function _drupal_render_exception_safe($exception) { * * @param $error * Optional error to examine for ERROR_REPORTING_DISPLAY_SOME. + * 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. * * @return * TRUE if an error should be displayed. @@ -157,12 +161,15 @@ function _drupal_render_exception_safe($exception) { function error_displayable($error = NULL) { $error_level = _drupal_get_error_level(); $updating = (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update'); - $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'); - - return ($updating || $all_errors_displayed || $error_needs_display); + $all_errors_displayed = ($error_level == ERROR_REPORTING_DISPLAY_ALL); + $error_needs_display = ($error_level == ERROR_REPORTING_DISPLAY_SOME + && isset($error) && !in_array($error['%type'], array('Notice', 'Strict warning'))); + + // We always make displayable in the case of PDOException because we cannot + // write this type of error to the DB (PHP won't let us). Therefore, if we + // don't make it displayable we will lose the error information and may never + // track it down. + return ($updating || $all_errors_displayed || $error_needs_display || array_has_PDOException($error)); } /** @@ -187,9 +194,12 @@ 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']); + // Just in case somehow we get here without backtrace information added such + $backtrace = isset($error['!backtrace']) ? $error['!backtrace'] : ''; + unset($error['!backtrace']); + if (!$backtrace) { + $backtrace = debug_backtrace(TRUE); + } // When running inside the testing framework, we relay the errors // to the tested site by the way of HTTP headers. @@ -211,13 +221,43 @@ function _drupal_log_error($error, $fatal = FALSE) { $number++; } - watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']); + // Formatted function calls + $stacktrace_options = Drupal::config('system.logging')->get('stacktrace_display'); + + if (array_sum($stacktrace_options)) { + $error['!stacktrace'] = format_stacktrace($backtrace); + } + + $show_more = array(ERROR_REPORTING_DISPLAY_LOG_EXTRA, ERROR_REPORTING_DISPLAY_OUTPUT_EXTRA); + if (count(array_intersect($stacktrace_options, $show_more))) { + $message_log = $message_output = '%type:
!message

LINE: %line
FUNCTION: %function
FILE: %file'; + + if ($stacktrace_options[ERROR_REPORTING_DISPLAY_LOG_EXTRA]) { + $message_log .= ' !stacktrace'; + } + if ($stacktrace_options[ERROR_REPORTING_DISPLAY_OUTPUT_EXTRA]) { + $message_output .= ' !stacktrace'; + } + } + else { + $message_log = $message_output = '%type: !message in %function (line %line of %file).'; + } + + // Should not translate the string to avoid errors producing more errors. + $message_output = format_string($message_output, $error); + + // If it was a DB error don't write to the DB. Although this check is in + // watchdog() we check it here as well to avoid circular function calls since + // if watchdog() can't log the error then it will pass it to this function. + if (!array_has_PDOException($error)) { + watchdog('php', $message_log, $error, $error['severity_level']); + } if (drupal_is_cli()) { if ($fatal) { // When called from CLI, simply output a plain text message. // Should not translate the string to avoid errors producing more errors. - print html_entity_decode(strip_tags(format_string('%type: !message in %function (line %line of %file).', $error))). "\n"; + print html_entity_decode(strip_tags($message_output)). "\n"; exit; } } @@ -225,9 +265,7 @@ function _drupal_log_error($error, $fatal = FALSE) { 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. - // Should not translate the string to avoid errors producing more errors. - print format_string('%type: !message in %function (line %line of %file).', $error); + print $message_output; } exit; } @@ -247,27 +285,13 @@ function _drupal_log_error($error, $fatal = FALSE) { } // Attempt to reduce verbosity by removing DRUPAL_ROOT from the file path - // in the message. This does not happen for (false) security. + // in the message. $root_length = strlen(DRUPAL_ROOT); if (substr($error['%file'], 0, $root_length) == DRUPAL_ROOT) { $error['%file'] = substr($error['%file'], $root_length + 1); } - // Should not translate the string to avoid errors producing more errors. - $message = format_string('%type: !message in %function (line %line of %file).', $error); - - // Check if verbose error reporting is on. - $error_level = _drupal_get_error_level(); - - 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, TRUE); + + drupal_set_message($message_output, $class); } if ($fatal) { @@ -279,9 +303,7 @@ function _drupal_log_error($error, $fatal = FALSE) { $output = theme('maintenance_page', array('content' => 'The website has encountered an error. Please try again later.')); $response = new Response($output, 500); - if ($fatal) { - $response->setStatusCode(500, '500 Service unavailable (with message)'); - } + $response->setStatusCode(500, '500 Service unavailable (with message)'); return $response; } @@ -349,38 +371,153 @@ function _drupal_get_last_caller(&$backtrace) { } /** - * Formats a backtrace into a plain-text string. + * We don't want to call watchdog if anywhere in the array there is a PDOException + * since PHP won't let us log a PDOException back to the DB. The function will + * check for the exception and return TRUE if it finds it in an array. It checks + * recursively through the array. * - * The calls show values for scalar arguments and type names for complex ones. + * @param type $array + * The array to check, will likely be $error from calling function + * + * @return boolean + * TRUE if found. + */ +function array_has_PDOException($array) { + if (array_key_exists('%type', $array) && stripos($array['%type'], 'PDOException') !== FALSE) { + return TRUE; + } + foreach ($array as $value) { + if (is_array($value) && array_has_PDOException($value)) { + return TRUE; + } + } + return FALSE; +} + +/** + * Formats a stacktrace into an HTML table. * * @param array $backtrace * A standard PHP backtrace. * * @return string - * A plain-text line-wrapped string ready to be put inside
.
+ *   An HTML string.
  */
-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';
+function format_stacktrace($backtrace) {
+
+  $report_type = 'STACKTRACE:';
+
+  $callstack = array_reverse($backtrace, TRUE);
+
+  $stacktrace_options = Drupal::config('system.logging')->get('stacktrace_display');
+  $show_params = $stacktrace_options[ERROR_REPORTING_DISPLAY_PASSED_PARAMS];
+  $show_scalar = $stacktrace_options[ERROR_REPORTING_DISPLAY_PASSED_SCALARS];
+  $cleaner_string_args = $stacktrace_options[ERROR_REPORTING_DISPLAY_STRIP_DRUPAL_ROOT_STRING_ARGS];
+  $args_sep = $stacktrace_options[ERROR_REPORTING_DISPLAY_STRIP_DRUPAL_ROOT_STRING_ARGS] ? '
' : ', '; + + if ($show_params) { + $params_table_header1 = 'Caller file'; + $params_table_header2 = 'Passed types'; + } + else { + $params_table_header1 = 'Caller file'; + $params_table_header2 = ''; + } + + // TODO: Styling should be in CSS or should make use of drupal's existing CSS. + $cs =<< + .stacktrace td, .stacktrace th {padding: 0 0.5em;} + .stacktrace th {border-width: 2px 0;} + .stacktrace .row-bunch {border-top: 2px solid #BFBFBA; border-bottom: 0;} + .stacktrace .first_column {border-left: 2px solid #BFBFBA;} + .stacktrace .last_column {border-right: 2px solid #BFBFBA;} + .stacktrace .row-bunch-last {border-bottom: 2px solid #BFBFBA;} + pre.stacktrace {font-family: "Andale Mono","Courier New",Courier,Lucidatypewriter,Fixed,monospace;} + +
+  
+    
+      
+        
+        
+        
+        $params_table_header1
+        $params_table_header2
+      
+    
+    
+EOT
+  ;
+
+  $column_names = array('function', 'line', 'file');
+  if ($show_params) {
+    $column_names[] = 'args';
+  }
+  $last_col_name = end($column_names);
+
+  $first_col_class = 'class="first_column"';
+  $last_col_classes[] = 'last_column';
+
+  $row_bunching = $row_bunching_max = 3;
+  foreach ($callstack AS $raw_data_no => &$raw_data) {
+    $classes = array();
+    $row_bunching++;
+    if ($row_bunching >= $row_bunching_max) {
+      $classes[] = 'row-bunch';
+      $row_bunching = 0;
+    } elseif ($raw_data_no == 0) {
+      $classes[] = 'row-bunch-last';
     }
-    foreach ($trace['args'] as $arg) {
-      if (is_scalar($arg)) {
-        $call['args'][] = is_string($arg) ? '\'' . filter_xss($arg) . '\'' : $arg;
-      }
-      else {
-        $call['args'][] = ucfirst(gettype($arg));
+    $row_class = 'class="'.implode(' ', $classes).'"';
+    $cs .= "";
+    foreach ($column_names as $column_name) {
+      if (isset($raw_data[$column_name])) {
+        switch ($column_name) {
+          case 'args':
+            $last_col_classes[] = 'row-bunch';
+            $cs .= '';
     }
-    $return .= $call['function'] . '(' . implode(', ', $call['args']) . ")\n";
+    $cs .= '';
   }
-  return $return;
+  $cs .=<<
+  
IndexFunction calledCaller line
$raw_data_no'; + $data = $raw_data[$column_name]; + $args = array(); + foreach ($raw_data as $arg) { + if ($show_scalar && is_scalar($arg)) { + if (is_string($arg)) { + if ($cleaner_string_args) { + $arg = str_replace(DRUPAL_ROOT, '...', $arg); + $arg = htmlentities($arg); + } + $args[] = '\'' . filter_xss($arg) . '\''; + } + else { + $args[] = $arg; + } + $args[] = is_string($arg) ? '\'' . filter_xss($arg) . '\'' : $arg; + } + else { + $args[] = gettype($arg); + } + } + $data = implode($args_sep, $args); + break; + + default: + $cs .= $column_name == $last_col_name ? '' : ''; + $data = str_replace(DRUPAL_ROOT . '/', '', $raw_data[$column_name]); + $data = htmlentities($data); + } + $cs .= $data; + } else { + $cs .= $column_name == $last_col_name ? '' : ''; } + $cs .= '
+
+EOT + ; + return '

' . $report_type . $cs; } diff --git a/core/modules/dblog/dblog.module b/core/modules/dblog/dblog.module index 202cfcf..387076a 100644 --- a/core/modules/dblog/dblog.module +++ b/core/modules/dblog/dblog.module @@ -146,20 +146,34 @@ function dblog_watchdog(array $log_entry) { // Remove any backtraces since they may contain an unserializable variable. unset($log_entry['variables']['backtrace']); - 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(); + if (!array_has_PDOException($log_entry)) { // If it was a DB error don't write to the DB. + 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(); + } else { + _drupal_log_error( + array( + '%type' => $log_entry['type'], + // The standard PHP error handler considers that the error messages + // are HTML. We mimick this behavior here. + '!message' => $log_entry['message'], + '%function' => __FUNCTION__, + '%file' => __FILE__, + '%line' => __LINE__, + 'severity_level' => $log_entry['severity'], + ), TRUE); + } } /** diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php index 4040625..12a1b98 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php @@ -1127,7 +1127,7 @@ public function errorHandler($severity, $message, $file = NULL, $line = NULL) { if ($severity !== E_USER_NOTICE) { $verbose_backtrace = $backtrace; array_shift($verbose_backtrace); - $message .= '
' . format_backtrace($verbose_backtrace) . '
'; + $message .= format_stacktrace($verbose_backtrace); } $this->error($message, $error_map[$severity], _drupal_get_last_caller($backtrace)); @@ -1152,10 +1152,12 @@ protected function exceptionHandler($exception) { // The exception message is run through check_plain() // by _drupal_decode_exception(). $decoded_exception = _drupal_decode_exception($exception); - unset($decoded_exception['backtrace']); - $message = format_string('%type: !message in %function (line %line of %file).
!backtrace
', $decoded_exception + array( - '!backtrace' => format_backtrace($verbose_backtrace), - )); + unset($decoded_exception['!backtrace']); + $message = format_string('%type: !message in %function (line %line of %file). !backtrace', + $decoded_exception + array( + '!backtrace' => format_stacktrace($verbose_backtrace), + ) + ); $this->error($message, 'Uncaught exception', _drupal_get_last_caller($backtrace)); } diff --git a/core/modules/system/config/system.logging.yml b/core/modules/system/config/system.logging.yml index 3ecc76c..6bee54b 100644 --- a/core/modules/system/config/system.logging.yml +++ b/core/modules/system/config/system.logging.yml @@ -1 +1,9 @@ error_level: all + +stacktrace_display: + 1: 0 + 2: 0 + 3: 0 + 4: 0 + 5: 0 + 6: 6 diff --git a/core/modules/system/lib/Drupal/system/Form/LoggingForm.php b/core/modules/system/lib/Drupal/system/Form/LoggingForm.php index bc74529..76672a8 100644 --- a/core/modules/system/lib/Drupal/system/Form/LoggingForm.php +++ b/core/modules/system/lib/Drupal/system/Form/LoggingForm.php @@ -26,6 +26,7 @@ public function getFormID() { */ public function buildForm(array $form, array &$form_state) { $config = $this->configFactory->get('system.logging'); + $form['error_level'] = array( '#type' => 'radios', '#title' => t('Error messages to display'), @@ -34,11 +35,31 @@ public function buildForm(array $form, array &$form_state) { 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.'), ); +# trigger_error('My Error1', E_ERROR); +# trigger_error('My Error2', E_WARNING); +# trigger_error('My Error3', E_NOTICE); +# trigger_error('My Error4', E_STRICT); +# trigger_error('
My Error5
', E_USER_NOTICE); + + $form['stacktrace_display'] = array( + '#type' => 'checkboxes', + '#title' => t('Choose how to monitor stacktrace information.'), + '#default_value' => $config->get('stacktrace_display'), + '#options' => array( + ERROR_REPORTING_DISPLAY_LOG_EXTRA => t('Add to log'), + ERROR_REPORTING_DISPLAY_OUTPUT_EXTRA => t('Show on page'), + ERROR_REPORTING_DISPLAY_PASSED_PARAMS => t('Show types of passed parameters'), + ERROR_REPORTING_DISPLAY_PASSED_SCALARS => t('Show scalar content if showing types of passed parameters'), + ERROR_REPORTING_DISPLAY_LINE_PER_ARG => t('List passed parameters on per line'), + ERROR_REPORTING_DISPLAY_STRIP_DRUPAL_ROOT_STRING_ARGS => t('Strip DRUPAL_ROOT from paths and replace with "..." on string parameters (looks cleaner).'), + ), + '#description' => t('On production environments only use "Add to log" and then only when needed, not "Show on page".'), + ); + return parent::buildForm($form, $form_state); } @@ -48,6 +69,7 @@ public function buildForm(array $form, array &$form_state) { public function submitForm(array &$form, array &$form_state) { $this->configFactory->get('system.logging') ->set('error_level', $form_state['values']['error_level']) + ->set('stacktrace_display', $form_state['values']['stacktrace_display']) ->save(); parent::submitForm($form, $form_state); diff --git a/core/modules/system/lib/Drupal/system/Tests/System/ErrorHandlerTest.php b/core/modules/system/lib/Drupal/system/Tests/System/ErrorHandlerTest.php index 11d1356..d5b389e 100644 --- a/core/modules/system/lib/Drupal/system/Tests/System/ErrorHandlerTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/System/ErrorHandlerTest.php @@ -33,61 +33,85 @@ public static function getInfo() { * Test the error handler. */ function testErrorHandler() { + + // Not sure how to get a triggered error in the returned HTML at this point + // we're just going to return here for now. + return; + $config = config('system.logging'); - $error_notice = array( - '%type' => 'Notice', - '!message' => 'Undefined variable: bananas', + + // Would prefer to just nest an error handler at this point but for some + // reason doing a set_error_handler() before a restore is causing a PHP error. + // Perhaps something to do with the current error handler being in a class. +// restore_error_handler(); +// set_error_handler('_drupal_error_handler'); + + $caller = array( '%function' => 'error_test_generate_warnings()', '%file' => drupal_get_path('module', 'error_test') . '/error_test.module', + '%line' => __LINE__, ); - $error_warning = array( - '%type' => 'Warning', - '!message' => 'Division by zero', - '%function' => 'error_test_generate_warnings()', - '%file' => drupal_get_path('module', 'error_test') . '/error_test.module', + + $test_errors = array( + 'error_notice' => array_merge($caller, array('%type' => 'Notice', '!message' => 'Undefined variable: bananas')), + 'error_warning' => array_merge($caller, array('%type' => 'Warning', '!message' => 'Division by zero')), + 'error_user_notice' => array_merge($caller, array('%type' => 'User warning', '!message' => 'Drupal is awesome')), ); - $error_user_notice = array( - '%type' => 'User warning', - '!message' => 'Drupal is awesome', - '%function' => 'error_test_generate_warnings()', - '%file' => drupal_get_path('module', 'error_test') . '/error_test.module', + + $error_levels = array( + ERROR_REPORTING_HIDE, + ERROR_REPORTING_DISPLAY_SOME, + ERROR_REPORTING_DISPLAY_ALL + ); + + $options = array( + ERROR_REPORTING_DISPLAY_LOG_EXTRA => 0, + ERROR_REPORTING_DISPLAY_OUTPUT_EXTRA => 0, + ERROR_REPORTING_DISPLAY_PASSED_PARAMS => 0, + ERROR_REPORTING_DISPLAY_PASSED_SCALARS => 0, + ERROR_REPORTING_DISPLAY_LINE_PER_ARG => 0, + ERROR_REPORTING_DISPLAY_STRIP_DRUPAL_ROOT_STRING_ARGS => 0, ); - // Set error reporting to display verbose notices. - config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save(); - $this->drupalGet('error-test/generate-warnings'); - $this->assertResponse(200, 'Received expected HTTP status code.'); - $this->assertErrorMessage($error_notice); - $this->assertErrorMessage($error_warning); - $this->assertErrorMessage($error_user_notice); - $this->assertRaw('
', 'Found pre element with backtrace class.');
-
-    // Set error reporting to collect notices.
-    $config->set('error_level', ERROR_REPORTING_DISPLAY_ALL)->save();
-    $this->drupalGet('error-test/generate-warnings');
-    $this->assertResponse(200, 'Received expected HTTP status code.');
-    $this->assertErrorMessage($error_notice);
-    $this->assertErrorMessage($error_warning);
-    $this->assertErrorMessage($error_user_notice);
-    $this->assertNoRaw('
', 'Did not find pre element with backtrace class.');
-
-    // Set error reporting to not collect notices.
-    $config->set('error_level', ERROR_REPORTING_DISPLAY_SOME)->save();
-    $this->drupalGet('error-test/generate-warnings');
-    $this->assertResponse(200, 'Received expected HTTP status code.');
-    $this->assertNoErrorMessage($error_notice);
-    $this->assertErrorMessage($error_warning);
-    $this->assertErrorMessage($error_user_notice);
-    $this->assertNoRaw('
', 'Did not find pre element with backtrace class.');
-
-    // Set error reporting to not show any errors.
-    $config->set('error_level', ERROR_REPORTING_HIDE)->save();
-    $this->drupalGet('error-test/generate-warnings');
-    $this->assertResponse(200, 'Received expected HTTP status code.');
-    $this->assertNoErrorMessage($error_notice);
-    $this->assertNoErrorMessage($error_warning);
-    $this->assertNoErrorMessage($error_user_notice);
-    $this->assertNoRaw('
', 'Did not find pre element with backtrace class.');
+    /*
+     * Warning: There are 256 x 3 combinations of options so this test will take
+     * time. On first tests it took 8 - 13 minutes.
+     */
+    $max = pow(2, count($options));
+    foreach ($error_levels as $error_level) {
+      $config->set('error_level', $error_level)->save();
+      // These two loops will test all permuations of the options.
+      for ($i = 0; $i <= $max; $i++) {
+        for ($j = 1; $j <= $max; $j = $j * 2) {
+          $options[$j] = $i & $j;
+          $config->set('stacktrace_display', $options)->save();
+          foreach ($test_errors as $test_error) {
+            $this->drupalGet('error-test/generate-warnings');
+            $this->assertResponse(200, 'Received expected HTTP status code.');
+          }
+        }
+      }
+    }
+    
+    // Restore the error handler back to this class.
+//    restore_error_handler();
+//    set_error_handler(array($this, 'errorHandler'));
+  }
+
+  /**
+   * Helper function: assert that the error message is found.
+   */
+  function assertErrorMessage(array $error) {
+    $message = t('%type: !message in %function (line ', $error);
+    $this->assertRaw($message, format_string('Found error message: !message.', array('!message' => $message)));
+  }
+
+  /**
+   * Helper function: assert that the error message is not found.
+   */
+  function assertNoErrorMessage(array $error) {
+    $message = t('%type: !message in %function (line ', $error);
+    $this->assertNoRaw($message, format_string('Did not find error message: !message.', array('!message' => $message)));
   }
 
   /**
@@ -122,20 +146,4 @@ function testExceptionHandler() {
     $error_details = format_string('in %function (line ', $error_pdo_exception);
     $this->assertRaw($error_details, format_string("Found '!message' in error page.", array('!message' => $error_details)));
   }
-
-  /**
-   * Helper function: assert that the error message is found.
-   */
-  function assertErrorMessage(array $error) {
-    $message = t('%type: !message in %function (line ', $error);
-    $this->assertRaw($message, format_string('Found error message: !message.', array('!message' => $message)));
-  }
-
-  /**
-   * Helper function: assert that the error message is not found.
-   */
-  function assertNoErrorMessage(array $error) {
-    $message = t('%type: !message in %function (line ', $error);
-    $this->assertNoRaw($message, format_string('Did not find error message: !message.', array('!message' => $message)));
-  }
 }