[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/exception/ -> MWExceptionHandler.php (source)

   1  <?php
   2  /**
   3   * This program is free software; you can redistribute it and/or modify
   4   * it under the terms of the GNU General Public License as published by
   5   * the Free Software Foundation; either version 2 of the License, or
   6   * (at your option) any later version.
   7   *
   8   * This program is distributed in the hope that it will be useful,
   9   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11   * GNU General Public License for more details.
  12   *
  13   * You should have received a copy of the GNU General Public License along
  14   * with this program; if not, write to the Free Software Foundation, Inc.,
  15   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16   * http://www.gnu.org/copyleft/gpl.html
  17   *
  18   * @file
  19   */
  20  
  21  /**
  22   * Handler class for MWExceptions
  23   * @ingroup Exception
  24   */
  25  class MWExceptionHandler {
  26      /**
  27       * Install an exception handler for MediaWiki exception types.
  28       */
  29  	public static function installHandler() {
  30          set_exception_handler( array( 'MWExceptionHandler', 'handle' ) );
  31      }
  32  
  33      /**
  34       * Report an exception to the user
  35       * @param Exception $e
  36       */
  37  	protected static function report( Exception $e ) {
  38          global $wgShowExceptionDetails;
  39  
  40          $cmdLine = MWException::isCommandLine();
  41  
  42          if ( $e instanceof MWException ) {
  43              try {
  44                  // Try and show the exception prettily, with the normal skin infrastructure
  45                  $e->report();
  46              } catch ( Exception $e2 ) {
  47                  // Exception occurred from within exception handler
  48                  // Show a simpler error message for the original exception,
  49                  // don't try to invoke report()
  50                  $message = "MediaWiki internal error.\n\n";
  51  
  52                  if ( $wgShowExceptionDetails ) {
  53                      $message .= 'Original exception: ' . self::getLogMessage( $e ) .
  54                          "\nBacktrace:\n" . self::getRedactedTraceAsString( $e ) .
  55                          "\n\nException caught inside exception handler: " . self::getLogMessage( $e2 ) .
  56                          "\nBacktrace:\n" . self::getRedactedTraceAsString( $e2 );
  57                  } else {
  58                      $message .= "Exception caught inside exception handler.\n\n" .
  59                          "Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " .
  60                          "to show detailed debugging information.";
  61                  }
  62  
  63                  $message .= "\n";
  64  
  65                  if ( $cmdLine ) {
  66                      self::printError( $message );
  67                  } else {
  68                      echo nl2br( htmlspecialchars( $message ) ) . "\n";
  69                  }
  70              }
  71          } else {
  72              $message = "Unexpected non-MediaWiki exception encountered, of type \"" .
  73                  get_class( $e ) . "\"";
  74  
  75              if ( $wgShowExceptionDetails ) {
  76                  $message .= "\n" . MWExceptionHandler::getLogMessage( $e ) . "\nBacktrace:\n" .
  77                      self::getRedactedTraceAsString( $e ) . "\n";
  78              }
  79  
  80              if ( $cmdLine ) {
  81                  self::printError( $message );
  82              } else {
  83                  echo nl2br( htmlspecialchars( $message ) ) . "\n";
  84              }
  85          }
  86      }
  87  
  88      /**
  89       * Print a message, if possible to STDERR.
  90       * Use this in command line mode only (see isCommandLine)
  91       *
  92       * @param string $message Failure text
  93       */
  94  	public static function printError( $message ) {
  95          # NOTE: STDERR may not be available, especially if php-cgi is used from the
  96          # command line (bug #15602). Try to produce meaningful output anyway. Using
  97          # echo may corrupt output to STDOUT though.
  98          if ( defined( 'STDERR' ) ) {
  99              fwrite( STDERR, $message );
 100          } else {
 101              echo $message;
 102          }
 103      }
 104  
 105      /**
 106       * If there are any open database transactions, roll them back and log
 107       * the stack trace of the exception that should have been caught so the
 108       * transaction could be aborted properly.
 109       * @since 1.23
 110       * @param Exception $e
 111       */
 112  	public static function rollbackMasterChangesAndLog( Exception $e ) {
 113          $factory = wfGetLBFactory();
 114          if ( $factory->hasMasterChanges() ) {
 115              wfDebugLog( 'Bug56269',
 116                  'Exception thrown with an uncommited database transaction: ' .
 117                      MWExceptionHandler::getLogMessage( $e ) . "\n" .
 118                      $e->getTraceAsString()
 119              );
 120              $factory->rollbackMasterChanges();
 121          }
 122      }
 123  
 124      /**
 125       * Exception handler which simulates the appropriate catch() handling:
 126       *
 127       *   try {
 128       *       ...
 129       *   } catch ( MWException $e ) {
 130       *       $e->report();
 131       *   } catch ( Exception $e ) {
 132       *       echo $e->__toString();
 133       *   }
 134       * @param Exception $e
 135       */
 136  	public static function handle( $e ) {
 137          global $wgFullyInitialised;
 138  
 139          self::rollbackMasterChangesAndLog( $e );
 140  
 141          self::report( $e );
 142  
 143          // Final cleanup
 144          if ( $wgFullyInitialised ) {
 145              try {
 146                  // uses $wgRequest, hence the $wgFullyInitialised condition
 147                  wfLogProfilingData();
 148              } catch ( Exception $e ) {
 149              }
 150          }
 151  
 152          // Exit value should be nonzero for the benefit of shell jobs
 153          exit( 1 );
 154      }
 155  
 156      /**
 157       * Generate a string representation of an exception's stack trace
 158       *
 159       * Like Exception::getTraceAsString, but replaces argument values with
 160       * argument type or class name.
 161       *
 162       * @param Exception $e
 163       * @return string
 164       */
 165  	public static function getRedactedTraceAsString( Exception $e ) {
 166          $text = '';
 167  
 168          foreach ( self::getRedactedTrace( $e ) as $level => $frame ) {
 169              if ( isset( $frame['file'] ) && isset( $frame['line'] ) ) {
 170                  $text .= "#{$level} {$frame['file']}({$frame['line']}): ";
 171              } else {
 172                  // 'file' and 'line' are unset for calls via call_user_func (bug 55634)
 173                  // This matches behaviour of Exception::getTraceAsString to instead
 174                  // display "[internal function]".
 175                  $text .= "#{$level} [internal function]: ";
 176              }
 177  
 178              if ( isset( $frame['class'] ) ) {
 179                  $text .= $frame['class'] . $frame['type'] . $frame['function'];
 180              } else {
 181                  $text .= $frame['function'];
 182              }
 183  
 184              if ( isset( $frame['args'] ) ) {
 185                  $text .= '(' . implode( ', ', $frame['args'] ) . ")\n";
 186              } else {
 187                  $text .= "()\n";
 188              }
 189          }
 190  
 191          $level = $level + 1;
 192          $text .= "#{$level} {main}";
 193  
 194          return $text;
 195      }
 196  
 197      /**
 198       * Return a copy of an exception's backtrace as an array.
 199       *
 200       * Like Exception::getTrace, but replaces each element in each frame's
 201       * argument array with the name of its class (if the element is an object)
 202       * or its type (if the element is a PHP primitive).
 203       *
 204       * @since 1.22
 205       * @param Exception $e
 206       * @return array
 207       */
 208  	public static function getRedactedTrace( Exception $e ) {
 209          return array_map( function ( $frame ) {
 210              if ( isset( $frame['args'] ) ) {
 211                  $frame['args'] = array_map( function ( $arg ) {
 212                      return is_object( $arg ) ? get_class( $arg ) : gettype( $arg );
 213                  }, $frame['args'] );
 214              }
 215              return $frame;
 216          }, $e->getTrace() );
 217      }
 218  
 219      /**
 220       * Get the ID for this error.
 221       *
 222       * The ID is saved so that one can match the one output to the user (when
 223       * $wgShowExceptionDetails is set to false), to the entry in the debug log.
 224       *
 225       * @since 1.22
 226       * @param Exception $e
 227       * @return string
 228       */
 229  	public static function getLogId( Exception $e ) {
 230          if ( !isset( $e->_mwLogId ) ) {
 231              $e->_mwLogId = wfRandomString( 8 );
 232          }
 233          return $e->_mwLogId;
 234      }
 235  
 236      /**
 237       * If the exception occurred in the course of responding to a request,
 238       * returns the requested URL. Otherwise, returns false.
 239       *
 240       * @since 1.23
 241       * @return string|bool
 242       */
 243  	public static function getURL() {
 244          global $wgRequest;
 245          if ( !isset( $wgRequest ) || $wgRequest instanceof FauxRequest ) {
 246              return false;
 247          }
 248          return $wgRequest->getRequestURL();
 249      }
 250  
 251      /**
 252       * Return the requested URL and point to file and line number from which the
 253       * exception occurred.
 254       *
 255       * @since 1.22
 256       * @param Exception $e
 257       * @return string
 258       */
 259  	public static function getLogMessage( Exception $e ) {
 260          $id = self::getLogId( $e );
 261          $file = $e->getFile();
 262          $line = $e->getLine();
 263          $message = $e->getMessage();
 264          $url = self::getURL() ?: '[no req]';
 265  
 266          return "[$id] $url   Exception from line $line of $file: $message";
 267      }
 268  
 269      /**
 270       * Serialize an Exception object to JSON.
 271       *
 272       * The JSON object will have keys 'id', 'file', 'line', 'message', and
 273       * 'url'. These keys map to string values, with the exception of 'line',
 274       * which is a number, and 'url', which may be either a string URL or or
 275       * null if the exception did not occur in the context of serving a web
 276       * request.
 277       *
 278       * If $wgLogExceptionBacktrace is true, it will also have a 'backtrace'
 279       * key, mapped to the array return value of Exception::getTrace, but with
 280       * each element in each frame's "args" array (if set) replaced with the
 281       * argument's class name (if the argument is an object) or type name (if
 282       * the argument is a PHP primitive).
 283       *
 284       * @par Sample JSON record ($wgLogExceptionBacktrace = false):
 285       * @code
 286       *  {
 287       *    "id": "c41fb419",
 288       *    "file": "/var/www/mediawiki/includes/cache/MessageCache.php",
 289       *    "line": 704,
 290       *    "message": "Non-string key given",
 291       *    "url": "/wiki/Main_Page"
 292       *  }
 293       * @endcode
 294       *
 295       * @par Sample JSON record ($wgLogExceptionBacktrace = true):
 296       * @code
 297       *  {
 298       *    "id": "dc457938",
 299       *    "file": "/vagrant/mediawiki/includes/cache/MessageCache.php",
 300       *    "line": 704,
 301       *    "message": "Non-string key given",
 302       *    "url": "/wiki/Main_Page",
 303       *    "backtrace": [{
 304       *      "file": "/vagrant/mediawiki/extensions/VisualEditor/VisualEditor.hooks.php",
 305       *      "line": 80,
 306       *      "function": "get",
 307       *      "class": "MessageCache",
 308       *      "type": "->",
 309       *      "args": ["array"]
 310       *    }]
 311       *  }
 312       * @endcode
 313       *
 314       * @since 1.23
 315       * @param Exception $e
 316       * @param bool $pretty Add non-significant whitespace to improve readability (default: false).
 317       * @param int $escaping Bitfield consisting of FormatJson::.*_OK class constants.
 318       * @return string|bool JSON string if successful; false upon failure
 319       */
 320  	public static function jsonSerializeException( Exception $e, $pretty = false, $escaping = 0 ) {
 321          global $wgLogExceptionBacktrace;
 322  
 323          $exceptionData = array(
 324              'id' => self::getLogId( $e ),
 325              'file' => $e->getFile(),
 326              'line' => $e->getLine(),
 327              'message' => $e->getMessage(),
 328          );
 329  
 330          // Because MediaWiki is first and foremost a web application, we set a
 331          // 'url' key unconditionally, but set it to null if the exception does
 332          // not occur in the context of a web request, as a way of making that
 333          // fact visible and explicit.
 334          $exceptionData['url'] = self::getURL() ?: null;
 335  
 336          if ( $wgLogExceptionBacktrace ) {
 337              // Argument values may not be serializable, so redact them.
 338              $exceptionData['backtrace'] = self::getRedactedTrace( $e );
 339          }
 340  
 341          return FormatJson::encode( $exceptionData, $pretty, $escaping );
 342      }
 343  
 344      /**
 345       * Log an exception to the exception log (if enabled).
 346       *
 347       * This method must not assume the exception is an MWException,
 348       * it is also used to handle PHP errors or errors from other libraries.
 349       *
 350       * @since 1.22
 351       * @param Exception $e
 352       */
 353  	public static function logException( Exception $e ) {
 354          global $wgLogExceptionBacktrace;
 355  
 356          if ( !( $e instanceof MWException ) || $e->isLoggable() ) {
 357              $log = self::getLogMessage( $e );
 358              if ( $wgLogExceptionBacktrace ) {
 359                  wfDebugLog( 'exception', $log . "\n" . $e->getTraceAsString() );
 360              } else {
 361                  wfDebugLog( 'exception', $log );
 362              }
 363  
 364              $json = self::jsonSerializeException( $e, false, FormatJson::ALL_OK );
 365              if ( $json !== false ) {
 366                  wfDebugLog( 'exception-json', $json, 'private' );
 367              }
 368          }
 369  
 370      }
 371  
 372  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1