MediaWiki  REL1_22
Debug.php
Go to the documentation of this file.
00001 <?php
00033 class MWDebug {
00034 
00040     protected static $log = array();
00041 
00047     protected static $debug = array();
00048 
00054     protected static $query = array();
00055 
00061     protected static $enabled = false;
00062 
00069     protected static $deprecationWarnings = array();
00070 
00077     public static function init() {
00078         self::$enabled = true;
00079     }
00080 
00088     public static function addModules( OutputPage $out ) {
00089         if ( self::$enabled ) {
00090             $out->addModules( 'mediawiki.debug.init' );
00091         }
00092     }
00093 
00102     public static function log( $str ) {
00103         if ( !self::$enabled ) {
00104             return;
00105         }
00106 
00107         self::$log[] = array(
00108             'msg' => htmlspecialchars( $str ),
00109             'type' => 'log',
00110             'caller' => wfGetCaller(),
00111         );
00112     }
00113 
00119     public static function getLog() {
00120         return self::$log;
00121     }
00122 
00127     public static function clearLog() {
00128         self::$log = array();
00129         self::$deprecationWarnings = array();
00130     }
00131 
00145     public static function warning( $msg, $callerOffset = 1, $level = E_USER_NOTICE, $log = 'auto' ) {
00146         global $wgDevelopmentWarnings;
00147 
00148         if ( $log === 'auto' && !$wgDevelopmentWarnings ) {
00149             $log = 'debug';
00150         }
00151 
00152         if ( $log === 'debug' ) {
00153             $level = false;
00154         }
00155 
00156         $callerDescription = self::getCallerDescription( $callerOffset );
00157 
00158         self::sendWarning( $msg, $callerDescription, $level );
00159 
00160         if ( self::$enabled ) {
00161             self::$log[] = array(
00162                 'msg' => htmlspecialchars( $msg ),
00163                 'type' => 'warn',
00164                 'caller' => $callerDescription['func'],
00165             );
00166         }
00167     }
00168 
00188     public static function deprecated( $function, $version = false, $component = false, $callerOffset = 2 ) {
00189         $callerDescription = self::getCallerDescription( $callerOffset );
00190         $callerFunc = $callerDescription['func'];
00191 
00192         $sendToLog = true;
00193 
00194         // Check to see if there already was a warning about this function
00195         if ( isset( self::$deprecationWarnings[$function][$callerFunc] ) ) {
00196             return;
00197         } elseif ( isset( self::$deprecationWarnings[$function] ) ) {
00198             if ( self::$enabled ) {
00199                 $sendToLog = false;
00200             } else {
00201                 return;
00202             }
00203         }
00204 
00205         self::$deprecationWarnings[$function][$callerFunc] = true;
00206 
00207         if ( $version ) {
00208             global $wgDeprecationReleaseLimit;
00209             if ( $wgDeprecationReleaseLimit && $component === false ) {
00210                 # Strip -* off the end of $version so that branches can use the
00211                 # format #.##-branchname to avoid issues if the branch is merged into
00212                 # a version of MediaWiki later than what it was branched from
00213                 $comparableVersion = preg_replace( '/-.*$/', '', $version );
00214 
00215                 # If the comparableVersion is larger than our release limit then
00216                 # skip the warning message for the deprecation
00217                 if ( version_compare( $wgDeprecationReleaseLimit, $comparableVersion, '<' ) ) {
00218                     $sendToLog = false;
00219                 }
00220             }
00221 
00222             $component = $component === false ? 'MediaWiki' : $component;
00223             $msg = "Use of $function was deprecated in $component $version.";
00224         } else {
00225             $msg = "Use of $function is deprecated.";
00226         }
00227 
00228         if ( $sendToLog ) {
00229             global $wgDevelopmentWarnings; // we could have a more specific $wgDeprecationWarnings setting.
00230             self::sendWarning( $msg, $callerDescription, $wgDevelopmentWarnings ? E_USER_DEPRECATED : false );
00231         }
00232 
00233         if ( self::$enabled ) {
00234             $logMsg = htmlspecialchars( $msg ) .
00235                 Html::rawElement( 'div', array( 'class' => 'mw-debug-backtrace' ),
00236                     Html::element( 'span', array(), 'Backtrace:' ) . wfBacktrace()
00237                 );
00238 
00239             self::$log[] = array(
00240                 'msg' => $logMsg,
00241                 'type' => 'deprecated',
00242                 'caller' => $callerFunc,
00243             );
00244         }
00245     }
00246 
00254     private static function getCallerDescription( $callerOffset ) {
00255         $callers = wfDebugBacktrace();
00256 
00257         if ( isset( $callers[$callerOffset] ) ) {
00258             $callerfile = $callers[$callerOffset];
00259             if ( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ) {
00260                 $file = $callerfile['file'] . ' at line ' . $callerfile['line'];
00261             } else {
00262                 $file = '(internal function)';
00263             }
00264         } else {
00265             $file = '(unknown location)';
00266         }
00267 
00268         if ( isset( $callers[$callerOffset + 1] ) ) {
00269             $callerfunc = $callers[$callerOffset + 1];
00270             $func = '';
00271             if ( isset( $callerfunc['class'] ) ) {
00272                 $func .= $callerfunc['class'] . '::';
00273             }
00274             if ( isset( $callerfunc['function'] ) ) {
00275                 $func .= $callerfunc['function'];
00276             }
00277         } else {
00278             $func = 'unknown';
00279         }
00280 
00281         return array( 'file' => $file, 'func' => $func );
00282     }
00283 
00292     private static function sendWarning( $msg, $caller, $level ) {
00293         $msg .= ' [Called from ' . $caller['func'] . ' in ' . $caller['file'] . ']';
00294 
00295         if ( $level !== false ) {
00296             trigger_error( $msg, $level );
00297         }
00298 
00299         wfDebug( "$msg\n" );
00300     }
00301 
00309     public static function debugMsg( $str ) {
00310         global $wgDebugComments, $wgShowDebug;
00311 
00312         if ( self::$enabled || $wgDebugComments || $wgShowDebug ) {
00313             self::$debug[] = rtrim( UtfNormal::cleanUp( $str ) );
00314         }
00315     }
00316 
00327     public static function query( $sql, $function, $isMaster ) {
00328         if ( !self::$enabled ) {
00329             return -1;
00330         }
00331 
00332         self::$query[] = array(
00333             'sql' => $sql,
00334             'function' => $function,
00335             'master' => (bool)$isMaster,
00336             'time' => 0.0,
00337             '_start' => microtime( true ),
00338         );
00339 
00340         return count( self::$query ) - 1;
00341     }
00342 
00349     public static function queryTime( $id ) {
00350         if ( $id === -1 || !self::$enabled ) {
00351             return;
00352         }
00353 
00354         self::$query[$id]['time'] = microtime( true ) - self::$query[$id]['_start'];
00355         unset( self::$query[$id]['_start'] );
00356     }
00357 
00364     protected static function getFilesIncluded( IContextSource $context ) {
00365         $files = get_included_files();
00366         $fileList = array();
00367         foreach ( $files as $file ) {
00368             $size = filesize( $file );
00369             $fileList[] = array(
00370                 'name' => $file,
00371                 'size' => $context->getLanguage()->formatSize( $size ),
00372             );
00373         }
00374 
00375         return $fileList;
00376     }
00377 
00385     public static function getDebugHTML( IContextSource $context ) {
00386         global $wgDebugComments;
00387 
00388         $html = '';
00389 
00390         if ( self::$enabled ) {
00391             MWDebug::log( 'MWDebug output complete' );
00392             $debugInfo = self::getDebugInfo( $context );
00393 
00394             // Cannot use OutputPage::addJsConfigVars because those are already outputted
00395             // by the time this method is called.
00396             $html = Html::inlineScript(
00397                 ResourceLoader::makeLoaderConditionalScript(
00398                     ResourceLoader::makeConfigSetScript( array( 'debugInfo' => $debugInfo ) )
00399                 )
00400             );
00401         }
00402 
00403         if ( $wgDebugComments ) {
00404             $html .= "<!-- Debug output:\n" .
00405                 htmlspecialchars( implode( "\n", self::$debug ) ) .
00406                 "\n\n-->";
00407         }
00408 
00409         return $html;
00410     }
00411 
00420     public static function getHTMLDebugLog() {
00421         global $wgDebugTimestamps, $wgShowDebug;
00422 
00423         if ( !$wgShowDebug ) {
00424             return '';
00425         }
00426 
00427         $curIdent = 0;
00428         $ret = "\n<hr />\n<strong>Debug data:</strong><ul id=\"mw-debug-html\">\n<li>";
00429 
00430         foreach ( self::$debug as $line ) {
00431             $pre = '';
00432             if ( $wgDebugTimestamps ) {
00433                 $matches = array();
00434                 if ( preg_match( '/^(\d+\.\d+ {1,3}\d+.\dM\s{2})/', $line, $matches ) ) {
00435                     $pre = $matches[1];
00436                     $line = substr( $line, strlen( $pre ) );
00437                 }
00438             }
00439             $display = ltrim( $line );
00440             $ident = strlen( $line ) - strlen( $display );
00441             $diff = $ident - $curIdent;
00442 
00443             $display = $pre . $display;
00444             if ( $display == '' ) {
00445                 $display = "\xc2\xa0";
00446             }
00447 
00448             if ( !$ident && $diff < 0 && substr( $display, 0, 9 ) != 'Entering ' && substr( $display, 0, 8 ) != 'Exiting ' ) {
00449                 $ident = $curIdent;
00450                 $diff = 0;
00451                 $display = '<span style="background:yellow;">' . nl2br( htmlspecialchars( $display ) ) . '</span>';
00452             } else {
00453                 $display = nl2br( htmlspecialchars( $display ) );
00454             }
00455 
00456             if ( $diff < 0 ) {
00457                 $ret .= str_repeat( "</li></ul>\n", -$diff ) . "</li><li>\n";
00458             } elseif ( $diff == 0 ) {
00459                 $ret .= "</li><li>\n";
00460             } else {
00461                 $ret .= str_repeat( "<ul><li>\n", $diff );
00462             }
00463             $ret .= "<tt>$display</tt>\n";
00464 
00465             $curIdent = $ident;
00466         }
00467 
00468         $ret .= str_repeat( '</li></ul>', $curIdent ) . "</li>\n</ul>\n";
00469 
00470         return $ret;
00471     }
00472 
00479     public static function appendDebugInfoToApiResult( IContextSource $context, ApiResult $result ) {
00480         if ( !self::$enabled ) {
00481             return;
00482         }
00483 
00484         // output errors as debug info, when display_errors is on
00485         // this is necessary for all non html output of the api, because that clears all errors first
00486         $obContents = ob_get_contents();
00487         if ( $obContents ) {
00488             $obContentArray = explode( '<br />', $obContents );
00489             foreach ( $obContentArray as $obContent ) {
00490                 if ( trim( $obContent ) ) {
00491                     self::debugMsg( Sanitizer::stripAllTags( $obContent ) );
00492                 }
00493             }
00494         }
00495 
00496         MWDebug::log( 'MWDebug output complete' );
00497         $debugInfo = self::getDebugInfo( $context );
00498 
00499         $result->setIndexedTagName( $debugInfo, 'debuginfo' );
00500         $result->setIndexedTagName( $debugInfo['log'], 'line' );
00501         $result->setIndexedTagName( $debugInfo['debugLog'], 'msg' );
00502         $result->setIndexedTagName( $debugInfo['queries'], 'query' );
00503         $result->setIndexedTagName( $debugInfo['includes'], 'queries' );
00504         $result->addValue( null, 'debuginfo', $debugInfo );
00505     }
00506 
00513     public static function getDebugInfo( IContextSource $context ) {
00514         if ( !self::$enabled ) {
00515             return array();
00516         }
00517 
00518         global $wgVersion, $wgRequestTime;
00519         $request = $context->getRequest();
00520         return array(
00521             'mwVersion' => $wgVersion,
00522             'phpVersion' => PHP_VERSION,
00523             'gitRevision' => GitInfo::headSHA1(),
00524             'gitBranch' => GitInfo::currentBranch(),
00525             'gitViewUrl' => GitInfo::headViewUrl(),
00526             'time' => microtime( true ) - $wgRequestTime,
00527             'log' => self::$log,
00528             'debugLog' => self::$debug,
00529             'queries' => self::$query,
00530             'request' => array(
00531                 'method' => $request->getMethod(),
00532                 'url' => $request->getRequestURL(),
00533                 'headers' => $request->getAllHeaders(),
00534                 'params' => $request->getValues(),
00535             ),
00536             'memory' => $context->getLanguage()->formatSize( memory_get_usage() ),
00537             'memoryPeak' => $context->getLanguage()->formatSize( memory_get_peak_usage() ),
00538             'includes' => self::getFilesIncluded( $context ),
00539         );
00540     }
00541 }