MediaWiki  REL1_24
MWExceptionHandler.php
Go to the documentation of this file.
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 }