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