[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |