MediaWiki  REL1_23
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 
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 }