MediaWiki
REL1_23
|
00001 <?php 00025 class MWExceptionHandler { 00029 public static function installHandler() { 00030 set_exception_handler( array( 'MWExceptionHandler', 'handle' ) ); 00031 } 00032 00036 protected static function report( Exception $e ) { 00037 global $wgShowExceptionDetails; 00038 00039 $cmdLine = MWException::isCommandLine(); 00040 00041 if ( $e instanceof MWException ) { 00042 try { 00043 // Try and show the exception prettily, with the normal skin infrastructure 00044 $e->report(); 00045 } catch ( Exception $e2 ) { 00046 // Exception occurred from within exception handler 00047 // Show a simpler error message for the original exception, 00048 // don't try to invoke report() 00049 $message = "MediaWiki internal error.\n\n"; 00050 00051 if ( $wgShowExceptionDetails ) { 00052 $message .= 'Original exception: ' . self::getLogMessage( $e ) . 00053 "\nBacktrace:\n" . self::getRedactedTraceAsString( $e ) . 00054 "\n\nException caught inside exception handler: " . self::getLogMessage( $e2 ) . 00055 "\nBacktrace:\n" . self::getRedactedTraceAsString( $e2 ); 00056 } else { 00057 $message .= "Exception caught inside exception handler.\n\n" . 00058 "Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " . 00059 "to show detailed debugging information."; 00060 } 00061 00062 $message .= "\n"; 00063 00064 if ( $cmdLine ) { 00065 self::printError( $message ); 00066 } else { 00067 echo nl2br( htmlspecialchars( $message ) ) . "\n"; 00068 } 00069 } 00070 } else { 00071 $message = "Unexpected non-MediaWiki exception encountered, of type \"" . 00072 get_class( $e ) . "\""; 00073 00074 if ( $wgShowExceptionDetails ) { 00075 $message .= "\n" . MWExceptionHandler::getLogMessage( $e ) . "\nBacktrace:\n" . 00076 self::getRedactedTraceAsString( $e ) . "\n"; 00077 } 00078 00079 if ( $cmdLine ) { 00080 self::printError( $message ); 00081 } else { 00082 echo nl2br( htmlspecialchars( $message ) ) . "\n"; 00083 } 00084 } 00085 } 00086 00093 public static function printError( $message ) { 00094 # NOTE: STDERR may not be available, especially if php-cgi is used from the 00095 # command line (bug #15602). Try to produce meaningful output anyway. Using 00096 # echo may corrupt output to STDOUT though. 00097 if ( defined( 'STDERR' ) ) { 00098 fwrite( STDERR, $message ); 00099 } else { 00100 echo $message; 00101 } 00102 } 00103 00111 public static function rollbackMasterChangesAndLog( Exception $e ) { 00112 $factory = wfGetLBFactory(); 00113 if ( $factory->hasMasterChanges() ) { 00114 wfDebugLog( 'Bug56269', 00115 'Exception thrown with an uncommited database transaction: ' . 00116 MWExceptionHandler::getLogMessage( $e ) . "\n" . 00117 $e->getTraceAsString() 00118 ); 00119 $factory->rollbackMasterChanges(); 00120 } 00121 } 00122 00134 public static function handle( $e ) { 00135 global $wgFullyInitialised; 00136 00137 self::rollbackMasterChangesAndLog( $e ); 00138 00139 self::report( $e ); 00140 00141 // Final cleanup 00142 if ( $wgFullyInitialised ) { 00143 try { 00144 // uses $wgRequest, hence the $wgFullyInitialised condition 00145 wfLogProfilingData(); 00146 } catch ( Exception $e ) { 00147 } 00148 } 00149 00150 // Exit value should be nonzero for the benefit of shell jobs 00151 exit( 1 ); 00152 } 00153 00163 public static function getRedactedTraceAsString( Exception $e ) { 00164 $text = ''; 00165 00166 foreach ( self::getRedactedTrace( $e ) as $level => $frame ) { 00167 if ( isset( $frame['file'] ) && isset( $frame['line'] ) ) { 00168 $text .= "#{$level} {$frame['file']}({$frame['line']}): "; 00169 } else { 00170 // 'file' and 'line' are unset for calls via call_user_func (bug 55634) 00171 // This matches behaviour of Exception::getTraceAsString to instead 00172 // display "[internal function]". 00173 $text .= "#{$level} [internal function]: "; 00174 } 00175 00176 if ( isset( $frame['class'] ) ) { 00177 $text .= $frame['class'] . $frame['type'] . $frame['function']; 00178 } else { 00179 $text .= $frame['function']; 00180 } 00181 00182 if ( isset( $frame['args'] ) ) { 00183 $text .= '(' . implode( ', ', $frame['args'] ) . ")\n"; 00184 } else { 00185 $text .= "()\n"; 00186 } 00187 } 00188 00189 $level = $level + 1; 00190 $text .= "#{$level} {main}"; 00191 00192 return $text; 00193 } 00194 00206 public static function getRedactedTrace( Exception $e ) { 00207 return array_map( function ( $frame ) { 00208 if ( isset( $frame['args'] ) ) { 00209 $frame['args'] = array_map( function ( $arg ) { 00210 return is_object( $arg ) ? get_class( $arg ) : gettype( $arg ); 00211 }, $frame['args'] ); 00212 } 00213 return $frame; 00214 }, $e->getTrace() ); 00215 } 00216 00227 public static function getLogId( Exception $e ) { 00228 if ( !isset( $e->_mwLogId ) ) { 00229 $e->_mwLogId = wfRandomString( 8 ); 00230 } 00231 return $e->_mwLogId; 00232 } 00233 00241 public static function getURL() { 00242 global $wgRequest; 00243 if ( !isset( $wgRequest ) || $wgRequest instanceof FauxRequest ) { 00244 return false; 00245 } 00246 return $wgRequest->getRequestURL(); 00247 } 00248 00257 public static function getLogMessage( Exception $e ) { 00258 $id = self::getLogId( $e ); 00259 $file = $e->getFile(); 00260 $line = $e->getLine(); 00261 $message = $e->getMessage(); 00262 $url = self::getURL() ?: '[no req]'; 00263 00264 return "[$id] $url Exception from line $line of $file: $message"; 00265 } 00266 00318 public static function jsonSerializeException( Exception $e, $pretty = false, $escaping = 0 ) { 00319 global $wgLogExceptionBacktrace; 00320 00321 $exceptionData = array( 00322 'id' => self::getLogId( $e ), 00323 'file' => $e->getFile(), 00324 'line' => $e->getLine(), 00325 'message' => $e->getMessage(), 00326 ); 00327 00328 // Because MediaWiki is first and foremost a web application, we set a 00329 // 'url' key unconditionally, but set it to null if the exception does 00330 // not occur in the context of a web request, as a way of making that 00331 // fact visible and explicit. 00332 $exceptionData['url'] = self::getURL() ?: null; 00333 00334 if ( $wgLogExceptionBacktrace ) { 00335 // Argument values may not be serializable, so redact them. 00336 $exceptionData['backtrace'] = self::getRedactedTrace( $e ); 00337 } 00338 00339 return FormatJson::encode( $exceptionData, $pretty, $escaping ); 00340 } 00341 00351 public static function logException( Exception $e ) { 00352 global $wgLogExceptionBacktrace; 00353 00354 if ( !( $e instanceof MWException ) || $e->isLoggable() ) { 00355 $log = self::getLogMessage( $e ); 00356 if ( $wgLogExceptionBacktrace ) { 00357 wfDebugLog( 'exception', $log . "\n" . $e->getTraceAsString() ); 00358 } else { 00359 wfDebugLog( 'exception', $log ); 00360 } 00361 00362 $json = self::jsonSerializeException( $e, false, FormatJson::ALL_OK ); 00363 if ( $json !== false ) { 00364 wfDebugLog( 'exception-json', $json, 'private' ); 00365 } 00366 } 00367 00368 } 00369 00370 }