Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.1011
diff -u -r1.1011 common.inc
--- includes/common.inc	9 Oct 2009 16:33:13 -0000	1.1011
+++ includes/common.inc	10 Oct 2009 17:18:59 -0000
@@ -1045,16 +1045,25 @@
       E_STRICT => 'Strict warning',
       E_RECOVERABLE_ERROR => 'Recoverable fatal error'
     );
-    $caller = _drupal_get_last_caller(debug_backtrace());
-
-    // We treat recoverable errors as fatal.
-    _drupal_log_error(array(
+    $caller = _drupal_get_last_caller();
+    $error = array(
       '%type' => isset($types[$error_level]) ? $types[$error_level] : 'Unknown error',
       '%message' => $message,
       '%function' => $caller['function'],
       '%file' => $caller['file'],
       '%line' => $caller['line'],
-    ), $error_level == E_RECOVERABLE_ERROR);
+    );
+
+    try {
+      watchdog('php', '%type: %message in %function (line %line of %file).', $error, WATCHDOG_ERROR);
+    }
+    catch (Exception $e) {
+      // Ignore any additional watchdog exception, as that probably means
+      // that the database was not initialized correctly.
+    }
+
+    // We treat recoverable errors as fatal.
+    _drupal_display_error($error, $error_level == E_RECOVERABLE_ERROR);
   }
 }
 
@@ -1070,60 +1079,19 @@
  */
 function _drupal_exception_handler($exception) {
   // Log the message to the watchdog and return an error page to the user.
-  _drupal_log_error(_drupal_decode_exception($exception), TRUE);
+  watchdog_exception($exception, 'Uncaught Exception', array(), WATCHDOG_ERROR);
+  _drupal_display_error(_drupal_decode_exception($exception), TRUE);
 }
 
 /**
- * Decode an exception, especially to retrive the correct caller.
- *
- * @param $exception
- *   The exception object that was thrown.
- * @return An error in the format expected by _drupal_log_error().
- */
-function _drupal_decode_exception($exception) {
-  $message = $exception->getMessage();
-
-  $backtrace = $exception->getTrace();
-  // Add the line throwing the exception to the backtrace.
-  array_unshift($backtrace, array('line' => $exception->getLine(), 'file' => $exception->getFile()));
-
-  // For PDOException errors, we try to return the initial caller,
-  // skipping internal functions of the database layer.
-  if ($exception instanceof PDOException) {
-    // The first element in the stack is the call, the second element gives us the caller.
-    // We skip calls that occurred in one of the classes of the database layer
-    // or in one of its global functions.
-    $db_functions = array('db_query',  'db_query_range');
-    while (!empty($backtrace[1]) && ($caller = $backtrace[1]) &&
-        ((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE)) ||
-        in_array($caller['function'], $db_functions))) {
-      // We remove that call.
-      array_shift($backtrace);
-    }
-    if (isset($exception->query_string, $exception->args)) {
-      $message .= ": " . $exception->query_string . "; " . print_r($exception->args, TRUE);
-    }
-  }
-  $caller = _drupal_get_last_caller($backtrace);
-
-  return array(
-    '%type' => get_class($exception),
-    '%message' => $message,
-    '%function' => $caller['function'],
-    '%file' => $caller['file'],
-    '%line' => $caller['line'],
-  );
-}
-
-/**
- * Log a PHP error or exception, display an error page in fatal cases.
+ * Display an appropriate error page for the current context.
  *
  * @param $error
  *   An array with the following keys: %type, %message, %function, %file, %line.
  * @param $fatal
  *   TRUE if the error is fatal.
  */
-function _drupal_log_error($error, $fatal = FALSE) {
+function _drupal_display_error($error, $fatal = FALSE) {
   // Initialize a maintenance theme if the boostrap was not complete.
   // Do it early because drupal_set_message() triggers a drupal_theme_initialize().
   if ($fatal && (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL)) {
@@ -1153,13 +1121,6 @@
     $number++;
   }
 
-  try {
-    watchdog('php', '%type: %message in %function (line %line of %file).', $error, WATCHDOG_ERROR);
-  }
-  catch (Exception $e) {
-    // Ignore any additional watchdog exception, as that probably means
-    // that the database was not initialized correctly.
-  }
 
   if ($fatal) {
     drupal_add_http_header('500 Service unavailable (with message)');
@@ -1204,11 +1165,17 @@
  * Gets the last caller from a backtrace.
  *
  * @param $backtrace
- *   A standard PHP backtrace.
+ *   A standard PHP backtrace. If not provided, it will use the current backtrace.
  * @return
  *   An associative array with keys 'file', 'line' and 'function'.
  */
-function _drupal_get_last_caller($backtrace) {
+function _drupal_get_last_caller($backtrace = NULL) {
+  if (!is_array($backtrace)) {
+    $backtrace = debug_backtrace();
+    // Remove _drupal_get_last_caller from the trace and advance the pointer.
+    array_shift($backtrace);
+  }
+
   // Errors that occur inside PHP internal functions do not generate
   // information about file and line. Ignore black listed functions.
   $blacklist = array('debug');
Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.309
diff -u -r1.309 bootstrap.inc
--- includes/bootstrap.inc	9 Oct 2009 16:33:13 -0000	1.309
+++ includes/bootstrap.inc	10 Oct 2009 17:18:54 -0000
@@ -1235,6 +1235,97 @@
 }
 
 /**
+ * Log a system message for an Exception.
+ *
+ * @param $exception
+ *   The Exception object to log.
+ * @param $message
+ *   Additional info to store in the log. Keep $message translatable
+ *   by not concatenating dynamic values into it! Variables in the
+ *   message should be added by using placeholder strings alongside
+ *   the variables argument to declare the value of the placeholders.
+ *   See t() for documentation on how $message and $variables interact.
+ * @param $variables
+ *   Array of variables to replace in the message on display or
+ *   NULL if message is already translated or not possible to
+ *   translate.
+ * @param $severity
+ *   The severity of the message, as per RFC 3164.
+ * @param $link
+ *   A link to associate with the message.
+ *
+ * @see watchdog()
+ */
+function watchdog_exception($exception, $message='', $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) {
+  // Grab info about where the Exception was thrown.
+  $exception_info = _drupal_decode_exception($exception);
+
+  // The watchdog_exception() caller indicates where the Exception was caught.
+  $caller = _drupal_get_last_caller();
+  $exception_info['%caller_function'] = $caller['function'];
+  $exception_info['%caller_file'] = $caller['file'];
+  $exception_info['%caller_line'] = $caller['line'];
+
+  $infoline = '%type: %message in %function (line %line of %file), logged by %caller_function (line %caller_line of %caller_file).';
+
+  // Allow the calling function to record additional info about the Exception.
+  if (!empty($message)) {
+    $infoline .= ' Additional info: ' . $message;
+    $exception_info += $variables;
+  }
+
+  try {
+    watchdog('Exception', $infoline, $exception_info, $severity, $link);
+  }
+  catch (Exception $e) {
+    // Ignore any additional watchdog exception, as that probably means
+    // that the database was not initialized correctly.
+  }
+}
+
+/**
+ * Decode an exception, especially to retrieve the correct caller.
+ *
+ * @param $exception
+ *   The exception object that was thrown.
+ * @return An error in the format expected by _drupal_display_error().
+ */
+function _drupal_decode_exception($exception) {
+  $message = $exception->getMessage();
+
+  $backtrace = $exception->getTrace();
+  // Add the line throwing the exception to the backtrace.
+  array_unshift($backtrace, array('line' => $exception->getLine(), 'file' => $exception->getFile()));
+
+  // For PDOException errors, we try to return the initial caller,
+  // skipping internal functions of the database layer.
+  if ($exception instanceof PDOException) {
+    // The first element in the stack is the call, the second element gives us the caller.
+    // We skip calls that occurred in one of the classes of the database layer
+    // or in one of its global functions.
+    $db_functions = array('db_query',  'db_query_range');
+    while (!empty($backtrace[1]) && ($caller = $backtrace[1]) &&
+        ((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE)) ||
+        in_array($caller['function'], $db_functions))) {
+      // We remove that call.
+      array_shift($backtrace);
+    }
+    if (isset($exception->query_string, $exception->args)) {
+      $message .= ": " . $exception->query_string . "; " . print_r($exception->args, TRUE);
+    }
+  }
+  $caller = _drupal_get_last_caller($backtrace);
+
+  return array(
+    '%type' => get_class($exception),
+    '%message' => $message,
+    '%function' => $caller['function'],
+    '%file' => $caller['file'],
+    '%line' => $caller['line'],
+  );
+}
+
+/**
  * Set a message which reflects the status of the performed operation.
  *
  * If the function is called with no arguments, this function returns all set
Index: modules/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.157
diff -u -r1.157 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php	10 Oct 2009 16:48:38 -0000	1.157
+++ modules/simpletest/drupal_web_test_case.php	10 Oct 2009 17:19:00 -0000
@@ -1294,7 +1294,7 @@
   /**
    * Reads headers and registers errors received from the tested site.
    *
-   * @see _drupal_log_error().
+   * @see _drupal_display_error().
    *
    * @param $curlHandler
    *   The cURL handler.
@@ -1305,7 +1305,7 @@
     $this->headers[] = $header;
 
     // Errors are being sent via X-Drupal-Assertion-* headers,
-    // generated by _drupal_log_error() in the exact form required
+    // generated by _drupal_display_error() in the exact form required
     // by DrupalWebTestCase::error().
     if (preg_match('/^X-Drupal-Assertion-[0-9]+: (.*)$/', $header, $matches)) {
       // Call DrupalWebTestCase::error() with the parameters from the header.
