Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.989
diff -u -r1.989 common.inc
--- includes/common.inc	15 Sep 2009 17:10:38 -0000	1.989
+++ includes/common.inc	16 Sep 2009 16:50:22 -0000
@@ -854,6 +854,15 @@
  *   The exception object that was thrown.
  */
 function _drupal_exception_handler($exception) {
+  if ($exception instanceof DrupalException) {
+    // Since the exception wasn't caught, increase the error level.
+    // Zero is the most severe, avoid overwriting it.
+    if ($exception->severity > WATCHDOG_ERROR) {
+      $exception->severity = WATCHDOG_ERROR;
+    }
+    // And set it to log.
+    $exception->shouldLog(TRUE);
+  }
   // Log the message to the watchdog and return an error page to the user.
   _drupal_log_error(_drupal_decode_exception($exception), TRUE);
 }
Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.304
diff -u -r1.304 bootstrap.inc
--- includes/bootstrap.inc	14 Sep 2009 07:43:11 -0000	1.304
+++ includes/bootstrap.inc	16 Sep 2009 16:50:21 -0000
@@ -226,6 +226,146 @@
  * @} End of "Title text filtering flags".
  */
 
+/**
+ * This Exception class is the base for all other Exception types in Drupal.
+ * By using this wrapper, you will allow global control over logging behavior,
+ * allow modules to create Exception handling hooks, etc.
+ *
+ * All custom Exception classes should extend DrupalException.
+ */
+class DrupalException extends Exception {
+  public static $loggedExceptions = array();
+
+  /**
+   * This has to default to FALSE so that if it gets turned on,
+   * the shutdown function will be registered correctly.
+   */
+  protected $shouldLog = FALSE;
+
+  /**
+   * This next block of variables are for watchdog logging.
+   */
+  public $variables = array();
+  public $severity = WATCHDOG_NOTICE;
+  public $link = NULL;
+
+  /**
+   * The parent class Exception has two parameters: $message and $code. Since
+   * core doesn't use the $code for anything, the DrupalException class accepts
+   * parameters similar to watchdog() for consistency.
+   *
+   * @param $message
+   *   The message 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.
+   */
+  function __construct($message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) {
+    // The Exception object expects the message and an integer code.
+    parent::__construct($message, $severity);
+    // Avoid replacing the variables array in the object with NULL, etc.
+    if (is_array($variables)) {
+      $this->variables = $variables;
+    }
+    $this->severity = $severity;
+    $this->link = $link;
+  }
+
+  /**
+   * Change shouldLog to a new value.  The variable shouldn't be changed
+   * directly, because a shutdown function may need to be registered.
+   *
+   * @param $should_log
+   *   Whether or not this exception should log.
+   */
+  public function shouldLog($should_log) {
+    static $registered = FALSE;
+
+    $exception_id = spl_object_hash($this);
+
+    // Avoid registering the shutdown function multiple times if called directly.
+    if ($should_log === TRUE) {
+      if ($registered !== TRUE) {
+        // Use a shutdown function instead of a destructor so that it will
+        // be called before the DB is destructed.
+        register_shutdown_function(array('DrupalException', 'shutdown'));
+        $registered = TRUE;
+      }
+
+      // Store the exception so that it is not destroyed by the garbage collector.
+      DrupalException::$loggedExceptions[$exception_id] = $this;
+    }
+    else {
+      unset(DrupalException::$loggedExceptions[$exception_id]);
+    }
+    $this->shouldLog = $should_log;
+  }
+
+  /**
+   * Merge new variables into the exception, without overwriting.
+   *
+   * @param $variables
+   *   Array of variables to replace in the message on display or
+   *   NULL if message is already translated or not possible to
+   *   translate.
+   *
+   * @see watchdog()
+   */
+  public function addVariables($variables) {
+    if (!is_array($variables)) {
+      return;
+    }
+    // Because addVariables can be called more than once in different scopes
+    // as the exception bubbles up, avoid blowing out the most specific data
+    // in the event of a namespacing collision.
+    $this->variables += $variables;
+  }
+
+  /**
+   * Shutdown function, gets called during shutdown but before DB connection
+   * is destroyed normally.
+   *
+   * Child classes can prevent or alter logging behavior by implementing this
+   * function and not calling parent.
+   */
+  public static function shutdown() {
+    foreach (DrupalException::$loggedExceptions as $exception) {
+      $exception->logToWatchdog();
+    }
+  }
+
+  /**
+   * Log this Exception to watchdog, unless shouldLog is FALSE.
+   */
+  public function logToWatchdog() {
+    if ($this->shouldLog === TRUE) {
+      // Use the class the object was instanciated with as the watchdog type.
+      // Typically it will be 'DrupalException' but sometimes a child class.
+      try {
+        watchdog(
+          get_class($this),
+          $this->getMessage(),
+          $this->variables,
+          $this->severity,
+          $this->link
+        );
+      }
+      catch (Exception $e) {
+        // Note: Could potentially log in a different way, such as to the
+        // Apache log if the database is down.
+      }
+    }
+  }
+}
 
 /**
  * Start the timer with the specified name. If you start and stop the same
@@ -1248,6 +1388,47 @@
 }
 
 /**
+ * Use this function to log an Exception consistently, regardless of type.
+ *
+ * @param $exception
+ *   The Exception object to log.
+ * @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, $variables = array(), $severity = NULL, $link = NULL) {
+  if ($exception instanceof DrupalException) {
+    // Add the additional data to the DrupalException object.
+    $exception->addVariables($variables);
+    // Don't change the severity of the exception unless provided
+    if ($severity) {
+      $exception->severity = $severity;
+    }
+    if ($link) {
+      $exception->link = $link;
+    }
+    // DrupalExceptions have a native logging system that needs to be enabled.
+    $exception->shouldLog(true);
+    return;
+  }
+
+  // Watchdog expects a valid level, default notice.
+  if ($severity === NULL) {
+    $severity = WATCHDOG_NOTICE;
+  }
+
+  // The $exception is a different type, pull info and log immediately.
+  watchdog(get_class($exception), $exception->getMessage(), $variables, $severity, $link);
+}
+
+/**
  * 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/dblog/dblog.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/dblog/dblog.admin.inc,v
retrieving revision 1.29
diff -u -r1.29 dblog.admin.inc
--- modules/dblog/dblog.admin.inc	25 Aug 2009 10:27:14 -0000	1.29
+++ modules/dblog/dblog.admin.inc	16 Sep 2009 16:50:22 -0000
@@ -167,6 +167,10 @@
         _dblog_format_message($dblog),
       ),
       array(
+        array('data' => t('Variables'), 'header' => TRUE),
+        _dblog_format_variables($dblog->variables),
+      ),
+      array(
         array('data' => t('Severity'), 'header' => TRUE),
         $severity[$dblog->severity],
       ),
@@ -221,7 +225,6 @@
   );
 }
 
-
 /**
  * List dblog administration filters that can be applied.
  */
@@ -266,6 +269,15 @@
   }
 }
 
+/**
+ * Returns a screen-printable view of the variables for this entry.
+ *
+ * @param $variables
+ *   Serialized variables array from watchdog row.
+ */
+function _dblog_format_variables($variables) {
+  return check_plain(var_export(unserialize($variables), TRUE));
+}
 
 /**
  * Return form for dblog administration filters.
