MediaWiki REL1_34
MWDebug.php
Go to the documentation of this file.
1<?php
24
33class MWDebug {
39 protected static $log = [];
40
46 protected static $debug = [];
47
53 protected static $query = [];
54
60 protected static $enabled = false;
61
68 protected static $deprecationWarnings = [];
69
73 public static function setup() {
74 global $wgDebugToolbar,
76
77 if (
78 // Easy to forget to falsify $wgDebugToolbar for static caches.
79 // If file cache or CDN cache is on, just disable this (DWIMD).
80 $wgUseCdn ||
82 // Keep MWDebug off on CLI. This prevents MWDebug from eating up
83 // all the memory for logging SQL queries in maintenance scripts.
85 ) {
86 return;
87 }
88
89 if ( $wgDebugToolbar ) {
90 self::init();
91 }
92 }
93
100 public static function init() {
101 self::$enabled = true;
102 }
103
109 public static function deinit() {
110 self::$enabled = false;
111 }
112
120 public static function addModules( OutputPage $out ) {
121 if ( self::$enabled ) {
122 $out->addModules( 'mediawiki.debug' );
123 }
124 }
125
132 public static function log( $str ) {
133 if ( !self::$enabled ) {
134 return;
135 }
136 if ( !is_string( $str ) ) {
137 $str = print_r( $str, true );
138 }
139 self::$log[] = [
140 'msg' => htmlspecialchars( $str ),
141 'type' => 'log',
142 'caller' => wfGetCaller(),
143 ];
144 }
145
151 public static function getLog() {
152 return self::$log;
153 }
154
159 public static function clearLog() {
160 self::$log = [];
161 self::$deprecationWarnings = [];
162 }
163
175 public static function warning( $msg, $callerOffset = 1, $level = E_USER_NOTICE, $log = 'auto' ) {
177
178 if ( $log === 'auto' && !$wgDevelopmentWarnings ) {
179 $log = 'debug';
180 }
181
182 if ( $log === 'debug' ) {
183 $level = false;
184 }
185
186 $callerDescription = self::getCallerDescription( $callerOffset );
187
188 self::sendMessage( $msg, $callerDescription, 'warning', $level );
189
190 if ( self::$enabled ) {
191 self::$log[] = [
192 'msg' => htmlspecialchars( $msg ),
193 'type' => 'warn',
194 'caller' => $callerDescription['func'],
195 ];
196 }
197 }
198
217 public static function deprecated( $function, $version = false,
218 $component = false, $callerOffset = 2
219 ) {
220 $callerDescription = self::getCallerDescription( $callerOffset );
221 $callerFunc = $callerDescription['func'];
222
223 $sendToLog = true;
224
225 // Check to see if there already was a warning about this function
226 if ( isset( self::$deprecationWarnings[$function][$callerFunc] ) ) {
227 return;
228 } elseif ( isset( self::$deprecationWarnings[$function] ) ) {
229 if ( self::$enabled ) {
230 $sendToLog = false;
231 } else {
232 return;
233 }
234 }
235
236 self::$deprecationWarnings[$function][$callerFunc] = true;
237
238 if ( $version ) {
240 if ( $wgDeprecationReleaseLimit && $component === false ) {
241 # Strip -* off the end of $version so that branches can use the
242 # format #.##-branchname to avoid issues if the branch is merged into
243 # a version of MediaWiki later than what it was branched from
244 $comparableVersion = preg_replace( '/-.*$/', '', $version );
245
246 # If the comparableVersion is larger than our release limit then
247 # skip the warning message for the deprecation
248 if ( version_compare( $wgDeprecationReleaseLimit, $comparableVersion, '<' ) ) {
249 $sendToLog = false;
250 }
251 }
252
253 $component = $component === false ? 'MediaWiki' : $component;
254 $msg = "Use of $function was deprecated in $component $version.";
255 } else {
256 $msg = "Use of $function is deprecated.";
257 }
258
259 if ( $sendToLog ) {
260 global $wgDevelopmentWarnings; // we could have a more specific $wgDeprecationWarnings setting.
261 self::sendMessage(
262 $msg,
263 $callerDescription,
264 'deprecated',
265 $wgDevelopmentWarnings ? E_USER_DEPRECATED : false
266 );
267 }
268
269 if ( self::$enabled ) {
270 $logMsg = htmlspecialchars( $msg ) .
271 Html::rawElement( 'div', [ 'class' => 'mw-debug-backtrace' ],
272 Html::element( 'span', [], 'Backtrace:' ) . wfBacktrace()
273 );
274
275 self::$log[] = [
276 'msg' => $logMsg,
277 'type' => 'deprecated',
278 'caller' => $callerFunc,
279 ];
280 }
281 }
282
290 private static function getCallerDescription( $callerOffset ) {
291 $callers = wfDebugBacktrace();
292
293 if ( isset( $callers[$callerOffset] ) ) {
294 $callerfile = $callers[$callerOffset];
295 if ( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ) {
296 $file = $callerfile['file'] . ' at line ' . $callerfile['line'];
297 } else {
298 $file = '(internal function)';
299 }
300 } else {
301 $file = '(unknown location)';
302 }
303
304 if ( isset( $callers[$callerOffset + 1] ) ) {
305 $callerfunc = $callers[$callerOffset + 1];
306 $func = '';
307 if ( isset( $callerfunc['class'] ) ) {
308 $func .= $callerfunc['class'] . '::';
309 }
310 if ( isset( $callerfunc['function'] ) ) {
311 $func .= $callerfunc['function'];
312 }
313 } else {
314 $func = 'unknown';
315 }
316
317 return [ 'file' => $file, 'func' => $func ];
318 }
319
329 private static function sendMessage( $msg, $caller, $group, $level ) {
330 $msg .= ' [Called from ' . $caller['func'] . ' in ' . $caller['file'] . ']';
331
332 if ( $level !== false ) {
333 trigger_error( $msg, $level );
334 }
335
336 wfDebugLog( $group, $msg, 'private' );
337 }
338
347 public static function debugMsg( $str, $context = [] ) {
349
350 if ( self::$enabled || $wgDebugComments || $wgShowDebug ) {
351 if ( $context ) {
352 $prefix = '';
353 if ( isset( $context['prefix'] ) ) {
354 $prefix = $context['prefix'];
355 } elseif ( isset( $context['channel'] ) && $context['channel'] !== 'wfDebug' ) {
356 $prefix = "[{$context['channel']}] ";
357 }
358 if ( isset( $context['seconds_elapsed'] ) && isset( $context['memory_used'] ) ) {
359 $prefix .= "{$context['seconds_elapsed']} {$context['memory_used']} ";
360 }
361 $str = LegacyLogger::interpolate( $str, $context );
362 $str = $prefix . $str;
363 }
364 self::$debug[] = rtrim( UtfNormal\Validator::cleanUp( $str ) );
365 }
366 }
367
378 public static function query( $sql, $function, $runTime, $dbhost ) {
379 if ( !self::$enabled ) {
380 return false;
381 }
382
383 // Replace invalid UTF-8 chars with a square UTF-8 character
384 // This prevents json_encode from erroring out due to binary SQL data
385 $sql = preg_replace(
386 '/(
387 [\xC0-\xC1] # Invalid UTF-8 Bytes
388 | [\xF5-\xFF] # Invalid UTF-8 Bytes
389 | \xE0[\x80-\x9F] # Overlong encoding of prior code point
390 | \xF0[\x80-\x8F] # Overlong encoding of prior code point
391 | [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start
392 | [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start
393 | [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start
394 | (?<=[\x0-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle
395 | (?<![\xC2-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF4]
396 | [\xF0-\xF4][\x80-\xBF]|[\xF0-\xF4][\x80-\xBF]{2})[\x80-\xBF] # Overlong Sequence
397 | (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF]) # Short 3 byte sequence
398 | (?<=[\xF0-\xF4])[\x80-\xBF](?![\x80-\xBF]{2}) # Short 4 byte sequence
399 | (?<=[\xF0-\xF4][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF]) # Short 4 byte sequence (2)
400 )/x',
401 '■',
402 $sql
403 );
404
405 // last check for invalid utf8
406 $sql = UtfNormal\Validator::cleanUp( $sql );
407
408 self::$query[] = [
409 'sql' => "$dbhost: $sql",
410 'function' => $function,
411 'time' => $runTime,
412 ];
413
414 return true;
415 }
416
423 protected static function getFilesIncluded( IContextSource $context ) {
424 $files = get_included_files();
425 $fileList = [];
426 foreach ( $files as $file ) {
427 $size = filesize( $file );
428 $fileList[] = [
429 'name' => $file,
430 'size' => $context->getLanguage()->formatSize( $size ),
431 ];
432 }
433
434 return $fileList;
435 }
436
444 public static function getDebugHTML( IContextSource $context ) {
445 global $wgDebugComments;
446
447 $html = '';
448
449 if ( self::$enabled ) {
450 self::log( 'MWDebug output complete' );
451 $debugInfo = self::getDebugInfo( $context );
452
453 // Cannot use OutputPage::addJsConfigVars because those are already outputted
454 // by the time this method is called.
455 $html = ResourceLoader::makeInlineScript(
456 ResourceLoader::makeConfigSetScript( [ 'debugInfo' => $debugInfo ] ),
457 $context->getOutput()->getCSPNonce()
458 );
459 }
460
461 if ( $wgDebugComments ) {
462 $html .= "<!-- Debug output:\n" .
463 htmlspecialchars( implode( "\n", self::$debug ), ENT_NOQUOTES ) .
464 "\n\n-->";
465 }
466
467 return $html;
468 }
469
478 public static function getHTMLDebugLog() {
479 global $wgShowDebug;
480
481 if ( !$wgShowDebug ) {
482 return '';
483 }
484
485 $ret = "\n<hr />\n<strong>Debug data:</strong><ul id=\"mw-debug-html\">\n";
486
487 foreach ( self::$debug as $line ) {
488 $display = nl2br( htmlspecialchars( trim( $line ) ) );
489
490 $ret .= "<li><code>$display</code></li>\n";
491 }
492
493 $ret .= '</ul>' . "\n";
494
495 return $ret;
496 }
497
504 public static function appendDebugInfoToApiResult( IContextSource $context, ApiResult $result ) {
505 if ( !self::$enabled ) {
506 return;
507 }
508
509 // output errors as debug info, when display_errors is on
510 // this is necessary for all non html output of the api, because that clears all errors first
511 $obContents = ob_get_contents();
512 if ( $obContents ) {
513 $obContentArray = explode( '<br />', $obContents );
514 foreach ( $obContentArray as $obContent ) {
515 if ( trim( $obContent ) ) {
516 self::debugMsg( Sanitizer::stripAllTags( $obContent ) );
517 }
518 }
519 }
520
521 self::log( 'MWDebug output complete' );
522 $debugInfo = self::getDebugInfo( $context );
523
524 ApiResult::setIndexedTagName( $debugInfo, 'debuginfo' );
525 ApiResult::setIndexedTagName( $debugInfo['log'], 'line' );
526 ApiResult::setIndexedTagName( $debugInfo['debugLog'], 'msg' );
527 ApiResult::setIndexedTagName( $debugInfo['queries'], 'query' );
528 ApiResult::setIndexedTagName( $debugInfo['includes'], 'queries' );
529 $result->addValue( null, 'debuginfo', $debugInfo );
530 }
531
538 public static function getDebugInfo( IContextSource $context ) {
539 if ( !self::$enabled ) {
540 return [];
541 }
542
543 global $wgVersion;
544 $request = $context->getRequest();
545
546 // HHVM's reported memory usage from memory_get_peak_usage()
547 // is not useful when passing false, but we continue passing
548 // false for consistency of historical data in zend.
549 // see: https://github.com/facebook/hhvm/issues/2257#issuecomment-39362246
550 $realMemoryUsage = wfIsHHVM();
551
552 $branch = GitInfo::currentBranch();
553 if ( GitInfo::isSHA1( $branch ) ) {
554 // If it's a detached HEAD, the SHA1 will already be
555 // included in the MW version, so don't show it.
556 $branch = false;
557 }
558
559 return [
560 'mwVersion' => $wgVersion,
561 'phpEngine' => wfIsHHVM() ? 'HHVM' : 'PHP',
562 'phpVersion' => wfIsHHVM() ? HHVM_VERSION : PHP_VERSION,
563 'gitRevision' => GitInfo::headSHA1(),
564 'gitBranch' => $branch,
565 'gitViewUrl' => GitInfo::headViewUrl(),
566 'time' => $request->getElapsedTime(),
567 'log' => self::$log,
568 'debugLog' => self::$debug,
569 'queries' => self::$query,
570 'request' => [
571 'method' => $request->getMethod(),
572 'url' => $request->getRequestURL(),
573 'headers' => $request->getAllHeaders(),
574 'params' => $request->getValues(),
575 ],
576 'memory' => $context->getLanguage()->formatSize( memory_get_usage( $realMemoryUsage ) ),
577 'memoryPeak' => $context->getLanguage()->formatSize( memory_get_peak_usage( $realMemoryUsage ) ),
578 'includes' => self::getFilesIncluded( $context ),
579 ];
580 }
581}
$wgUseCdn
Enable/disable CDN.
$wgDeprecationReleaseLimit
Release limitation to wfDeprecated warnings, if set to a release number development warnings will not...
$wgDebugComments
Send debug data to an HTML comment in the output.
$wgVersion
MediaWiki version number.
$wgShowDebug
Display debug data at the bottom of the main content area.
$wgDebugToolbar
Display the new debugging toolbar.
$wgDevelopmentWarnings
If set to true MediaWiki will throw notices for some possible error conditions and for deprecated fun...
$wgUseFileCache
This will cache static pages for non-logged-in users to reduce database traffic on public sites.
global $wgCommandLineMode
wfBacktrace( $raw=null)
Get a debug backtrace as a string.
wfGetCaller( $level=2)
Get the name of the function which called this function wfGetCaller( 1 ) is the function with the wfG...
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfDebugBacktrace( $limit=0)
Safety wrapper for debug_backtrace().
wfIsHHVM()
Check if we are running under HHVM.
$debug
Definition Setup.php:783
$line
Definition cdb.php:59
This class represents the result of the API operations.
Definition ApiResult.php:35
addValue( $path, $name, $value, $flags=0)
Add value to the output data at the given path.
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
static headSHA1()
Definition GitInfo.php:392
static headViewUrl()
Definition GitInfo.php:408
static currentBranch()
Definition GitInfo.php:400
static isSHA1( $str)
Check if a string looks like a hex encoded SHA1 hash.
Definition GitInfo.php:158
New debugger system that outputs a toolbar on page view.
Definition MWDebug.php:33
static warning( $msg, $callerOffset=1, $level=E_USER_NOTICE, $log='auto')
Adds a warning entry to the log.
Definition MWDebug.php:175
static getFilesIncluded(IContextSource $context)
Returns a list of files included, along with their size.
Definition MWDebug.php:423
static $enabled
Is the debugger enabled?
Definition MWDebug.php:60
static setup()
Definition MWDebug.php:73
static addModules(OutputPage $out)
Add ResourceLoader modules to the OutputPage object if debugging is enabled.
Definition MWDebug.php:120
static appendDebugInfoToApiResult(IContextSource $context, ApiResult $result)
Append the debug info to given ApiResult.
Definition MWDebug.php:504
static $deprecationWarnings
Array of functions that have already been warned, formatted function-caller to prevent a buttload of ...
Definition MWDebug.php:68
static clearLog()
Clears internal log array and deprecation tracking.
Definition MWDebug.php:159
static deinit()
Disable the debugger.
Definition MWDebug.php:109
static query( $sql, $function, $runTime, $dbhost)
Begins profiling on a database query.
Definition MWDebug.php:378
static getLog()
Returns internal log array.
Definition MWDebug.php:151
static log( $str)
Adds a line to the log.
Definition MWDebug.php:132
static getDebugInfo(IContextSource $context)
Returns the HTML to add to the page for the toolbar.
Definition MWDebug.php:538
static debugMsg( $str, $context=[])
This is a method to pass messages from wfDebug to the pretty debugger.
Definition MWDebug.php:347
static getCallerDescription( $callerOffset)
Get an array describing the calling function at a specified offset.
Definition MWDebug.php:290
static $log
Log lines.
Definition MWDebug.php:39
static deprecated( $function, $version=false, $component=false, $callerOffset=2)
Show a warning that $function is deprecated.
Definition MWDebug.php:217
static $query
SQL statements of the database queries.
Definition MWDebug.php:53
static sendMessage( $msg, $caller, $group, $level)
Send a message to the debug log and optionally also trigger a PHP error, depending on the $level argu...
Definition MWDebug.php:329
static getHTMLDebugLog()
Generate debug log in HTML for displaying at the bottom of the main content area.
Definition MWDebug.php:478
static init()
Enabled the debugger and load resource module.
Definition MWDebug.php:100
static getDebugHTML(IContextSource $context)
Returns the HTML to add to the page for the toolbar.
Definition MWDebug.php:444
PSR-3 logger that mimics the historic implementation of MediaWiki's former wfErrorLog logging impleme...
This is one of the Core classes and should be read at least once by any new developers.
addModules( $modules)
Load one or more ResourceLoader modules on this page.
Interface for objects which can provide a MediaWiki context on request.
$context
Definition load.php:45
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42