MediaWiki REL1_35
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 protected static $deprecationFilters = [];
74
78 public static function setup() {
79 global $wgDebugToolbar,
81
82 if (
83 // Easy to forget to falsify $wgDebugToolbar for static caches.
84 // If file cache or CDN cache is on, just disable this (DWIMD).
85 $wgUseCdn ||
87 // Keep MWDebug off on CLI. This prevents MWDebug from eating up
88 // all the memory for logging SQL queries in maintenance scripts.
90 ) {
91 return;
92 }
93
94 if ( $wgDebugToolbar ) {
95 self::init();
96 }
97 }
98
105 public static function init() {
106 self::$enabled = true;
107 }
108
114 public static function deinit() {
115 self::$enabled = false;
116 }
117
125 public static function addModules( OutputPage $out ) {
126 if ( self::$enabled ) {
127 $out->addModules( 'mediawiki.debug' );
128 }
129 }
130
137 public static function log( $str ) {
138 if ( !self::$enabled ) {
139 return;
140 }
141 if ( !is_string( $str ) ) {
142 $str = print_r( $str, true );
143 }
144 self::$log[] = [
145 'msg' => htmlspecialchars( $str ),
146 'type' => 'log',
147 'caller' => wfGetCaller(),
148 ];
149 }
150
156 public static function getLog() {
157 return self::$log;
158 }
159
164 public static function clearLog() {
165 self::$log = [];
166 self::$deprecationWarnings = [];
167 }
168
180 public static function warning( $msg, $callerOffset = 1, $level = E_USER_NOTICE, $log = 'auto' ) {
182
183 if ( $log === 'auto' && !$wgDevelopmentWarnings ) {
184 $log = 'debug';
185 }
186
187 if ( $log === 'debug' ) {
188 $level = false;
189 }
190
191 $callerDescription = self::getCallerDescription( $callerOffset );
192
193 self::sendMessage(
194 self::formatCallerDescription( $msg, $callerDescription ),
195 'warning',
196 $level );
197
198 if ( self::$enabled ) {
199 self::$log[] = [
200 'msg' => htmlspecialchars( $msg ),
201 'type' => 'warn',
202 'caller' => $callerDescription['func'],
203 ];
204 }
205 }
206
225 public static function deprecated( $function, $version = false,
226 $component = false, $callerOffset = 2
227 ) {
228 if ( $version ) {
229 $component = $component ?: 'MediaWiki';
230 $msg = "Use of $function was deprecated in $component $version.";
231 } else {
232 $msg = "Use of $function is deprecated.";
233 }
234 self::deprecatedMsg( $msg, $version, $component, $callerOffset + 1 );
235 }
236
257 public static function deprecatedMsg( $msg, $version = false,
258 $component = false, $callerOffset = 2
259 ) {
260 if ( $callerOffset === false ) {
261 $callerFunc = '';
262 $rawMsg = $msg;
263 } else {
264 $callerDescription = self::getCallerDescription( $callerOffset );
265 $callerFunc = $callerDescription['func'];
266 $rawMsg = self::formatCallerDescription( $msg, $callerDescription );
267 }
268
269 $sendToLog = true;
270
271 // Check to see if there already was a warning about this function
272 if ( isset( self::$deprecationWarnings[$msg][$callerFunc] ) ) {
273 return;
274 } elseif ( isset( self::$deprecationWarnings[$msg] ) ) {
275 if ( self::$enabled ) {
276 $sendToLog = false;
277 } else {
278 return;
279 }
280 }
281
282 self::$deprecationWarnings[$msg][$callerFunc] = true;
283
284 if ( $version ) {
286
287 $component = $component ?: 'MediaWiki';
288 if ( $wgDeprecationReleaseLimit && $component === 'MediaWiki' ) {
289 # Strip -* off the end of $version so that branches can use the
290 # format #.##-branchname to avoid issues if the branch is merged into
291 # a version of MediaWiki later than what it was branched from
292 $comparableVersion = preg_replace( '/-.*$/', '', $version );
293
294 # If the comparableVersion is larger than our release limit then
295 # skip the warning message for the deprecation
296 if ( version_compare( $wgDeprecationReleaseLimit, $comparableVersion, '<' ) ) {
297 $sendToLog = false;
298 }
299 }
300 }
301
302 self::sendRawDeprecated(
303 $rawMsg,
304 $sendToLog,
305 $callerFunc
306 );
307 }
308
322 public static function sendRawDeprecated( $msg, $sendToLog = true, $callerFunc = '' ) {
323 foreach ( self::$deprecationFilters as $filter ) {
324 if ( preg_match( $filter, $msg ) ) {
325 return;
326 }
327 }
328
329 if ( $sendToLog ) {
330 global $wgDevelopmentWarnings; // we could have a more specific $wgDeprecationWarnings setting.
331 self::sendMessage(
332 $msg,
333 'deprecated',
334 $wgDevelopmentWarnings ? E_USER_DEPRECATED : false
335 );
336 }
337
338 if ( self::$enabled ) {
339 $logMsg = htmlspecialchars( $msg ) .
340 Html::rawElement( 'div', [ 'class' => 'mw-debug-backtrace' ],
341 Html::element( 'span', [], 'Backtrace:' ) . wfBacktrace()
342 );
343
344 self::$log[] = [
345 'msg' => $logMsg,
346 'type' => 'deprecated',
347 'caller' => $callerFunc,
348 ];
349 }
350 }
351
358 public static function filterDeprecationForTest( $regex ) {
359 if ( !defined( 'MW_PHPUNIT_TEST' ) && !defined( 'MW_PARSER_TEST' ) ) {
360 throw new RuntimeException( __METHOD__ . ' can only be used in tests' );
361 }
362 self::$deprecationFilters[] = $regex;
363 }
364
368 public static function clearDeprecationFilters() {
369 self::$deprecationFilters = [];
370 }
371
379 private static function getCallerDescription( $callerOffset ) {
380 $callers = wfDebugBacktrace();
381
382 if ( isset( $callers[$callerOffset] ) ) {
383 $callerfile = $callers[$callerOffset];
384 if ( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ) {
385 $file = $callerfile['file'] . ' at line ' . $callerfile['line'];
386 } else {
387 $file = '(internal function)';
388 }
389 } else {
390 $file = '(unknown location)';
391 }
392
393 if ( isset( $callers[$callerOffset + 1] ) ) {
394 $callerfunc = $callers[$callerOffset + 1];
395 $func = '';
396 if ( isset( $callerfunc['class'] ) ) {
397 $func .= $callerfunc['class'] . '::';
398 }
399 if ( isset( $callerfunc['function'] ) ) {
400 $func .= $callerfunc['function'];
401 }
402 } else {
403 $func = 'unknown';
404 }
405
406 return [ 'file' => $file, 'func' => $func ];
407 }
408
416 private static function formatCallerDescription( $msg, $caller ) {
417 return $msg . ' [Called from ' . $caller['func'] . ' in ' . $caller['file'] . ']';
418 }
419
428 private static function sendMessage( $msg, $group, $level ) {
429 if ( $level !== false ) {
430 trigger_error( $msg, $level );
431 }
432
433 wfDebugLog( $group, $msg );
434 }
435
444 public static function debugMsg( $str, $context = [] ) {
446
447 if ( self::$enabled || $wgDebugComments || $wgShowDebug ) {
448 if ( $context ) {
449 $prefix = '';
450 if ( isset( $context['prefix'] ) ) {
451 $prefix = $context['prefix'];
452 } elseif ( isset( $context['channel'] ) && $context['channel'] !== 'wfDebug' ) {
453 $prefix = "[{$context['channel']}] ";
454 }
455 if ( isset( $context['seconds_elapsed'] ) && isset( $context['memory_used'] ) ) {
456 $prefix .= "{$context['seconds_elapsed']} {$context['memory_used']} ";
457 }
458 $str = LegacyLogger::interpolate( $str, $context );
459 $str = $prefix . $str;
460 }
461 self::$debug[] = rtrim( UtfNormal\Validator::cleanUp( $str ) );
462 }
463 }
464
475 public static function query( $sql, $function, $runTime, $dbhost ) {
476 if ( !self::$enabled ) {
477 return false;
478 }
479
480 // Replace invalid UTF-8 chars with a square UTF-8 character
481 // This prevents json_encode from erroring out due to binary SQL data
482 $sql = preg_replace(
483 '/(
484 [\xC0-\xC1] # Invalid UTF-8 Bytes
485 | [\xF5-\xFF] # Invalid UTF-8 Bytes
486 | \xE0[\x80-\x9F] # Overlong encoding of prior code point
487 | \xF0[\x80-\x8F] # Overlong encoding of prior code point
488 | [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start
489 | [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start
490 | [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start
491 | (?<=[\x0-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle
492 | (?<![\xC2-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF4]
493 | [\xF0-\xF4][\x80-\xBF]|[\xF0-\xF4][\x80-\xBF]{2})[\x80-\xBF] # Overlong Sequence
494 | (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF]) # Short 3 byte sequence
495 | (?<=[\xF0-\xF4])[\x80-\xBF](?![\x80-\xBF]{2}) # Short 4 byte sequence
496 | (?<=[\xF0-\xF4][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF]) # Short 4 byte sequence (2)
497 )/x',
498 '■',
499 $sql
500 );
501
502 // last check for invalid utf8
503 $sql = UtfNormal\Validator::cleanUp( $sql );
504
505 self::$query[] = [
506 'sql' => "$dbhost: $sql",
507 'function' => $function,
508 'time' => $runTime,
509 ];
510
511 return true;
512 }
513
520 protected static function getFilesIncluded( IContextSource $context ) {
521 $files = get_included_files();
522 $fileList = [];
523 foreach ( $files as $file ) {
524 $size = filesize( $file );
525 $fileList[] = [
526 'name' => $file,
527 'size' => $context->getLanguage()->formatSize( $size ),
528 ];
529 }
530
531 return $fileList;
532 }
533
541 public static function getDebugHTML( IContextSource $context ) {
542 global $wgDebugComments;
543
544 $html = '';
545
546 if ( self::$enabled ) {
547 self::log( 'MWDebug output complete' );
548 $debugInfo = self::getDebugInfo( $context );
549
550 // Cannot use OutputPage::addJsConfigVars because those are already outputted
551 // by the time this method is called.
553 ResourceLoader::makeConfigSetScript( [ 'debugInfo' => $debugInfo ] ),
554 $context->getOutput()->getCSP()->getNonce()
555 );
556 }
557
558 if ( $wgDebugComments ) {
559 $html .= "<!-- Debug output:\n" .
560 htmlspecialchars( implode( "\n", self::$debug ), ENT_NOQUOTES ) .
561 "\n\n-->";
562 }
563
564 return $html;
565 }
566
575 public static function getHTMLDebugLog() {
576 global $wgShowDebug;
577
578 if ( !$wgShowDebug ) {
579 return '';
580 }
581
582 $ret = "\n<hr />\n<strong>Debug data:</strong><ul id=\"mw-debug-html\">\n";
583
584 foreach ( self::$debug as $line ) {
585 $display = nl2br( htmlspecialchars( trim( $line ) ) );
586
587 $ret .= "<li><code>$display</code></li>\n";
588 }
589
590 $ret .= '</ul>' . "\n";
591
592 return $ret;
593 }
594
601 public static function appendDebugInfoToApiResult( IContextSource $context, ApiResult $result ) {
602 if ( !self::$enabled ) {
603 return;
604 }
605
606 // output errors as debug info, when display_errors is on
607 // this is necessary for all non html output of the api, because that clears all errors first
608 $obContents = ob_get_contents();
609 if ( $obContents ) {
610 $obContentArray = explode( '<br />', $obContents );
611 foreach ( $obContentArray as $obContent ) {
612 if ( trim( $obContent ) ) {
613 self::debugMsg( Sanitizer::stripAllTags( $obContent ) );
614 }
615 }
616 }
617
618 self::log( 'MWDebug output complete' );
619 $debugInfo = self::getDebugInfo( $context );
620
621 ApiResult::setIndexedTagName( $debugInfo, 'debuginfo' );
622 ApiResult::setIndexedTagName( $debugInfo['log'], 'line' );
623 ApiResult::setIndexedTagName( $debugInfo['debugLog'], 'msg' );
624 ApiResult::setIndexedTagName( $debugInfo['queries'], 'query' );
625 ApiResult::setIndexedTagName( $debugInfo['includes'], 'queries' );
626 $result->addValue( null, 'debuginfo', $debugInfo );
627 }
628
635 public static function getDebugInfo( IContextSource $context ) {
636 if ( !self::$enabled ) {
637 return [];
638 }
639
640 $request = $context->getRequest();
641
642 $branch = GitInfo::currentBranch();
643 if ( GitInfo::isSHA1( $branch ) ) {
644 // If it's a detached HEAD, the SHA1 will already be
645 // included in the MW version, so don't show it.
646 $branch = false;
647 }
648
649 return [
650 'mwVersion' => MW_VERSION,
651 'phpEngine' => 'PHP',
652 'phpVersion' => PHP_VERSION,
653 'gitRevision' => GitInfo::headSHA1(),
654 'gitBranch' => $branch,
655 'gitViewUrl' => GitInfo::headViewUrl(),
656 'time' => $request->getElapsedTime(),
657 'log' => self::$log,
658 'debugLog' => self::$debug,
659 'queries' => self::$query,
660 'request' => [
661 'method' => $request->getMethod(),
662 'url' => $request->getRequestURL(),
663 'headers' => $request->getAllHeaders(),
664 'params' => $request->getValues(),
665 ],
666 'memory' => $context->getLanguage()->formatSize( memory_get_usage() ),
667 'memoryPeak' => $context->getLanguage()->formatSize( memory_get_peak_usage() ),
668 'includes' => self::getFilesIncluded( $context ),
669 ];
670 }
671}
$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.
$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.
const MW_VERSION
The running version of MediaWiki.
Definition Defines.php:40
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().
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 headSHA1()
Definition GitInfo.php:402
static headViewUrl()
Definition GitInfo.php:418
static currentBranch()
Definition GitInfo.php:410
static isSHA1( $str)
Check if a string looks like a hex encoded SHA1 hash.
Definition GitInfo.php:165
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:180
static getFilesIncluded(IContextSource $context)
Returns a list of files included, along with their size.
Definition MWDebug.php:520
static $enabled
Is the debugger enabled?
Definition MWDebug.php:60
static setup()
Definition MWDebug.php:78
static addModules(OutputPage $out)
Add ResourceLoader modules to the OutputPage object if debugging is enabled.
Definition MWDebug.php:125
static appendDebugInfoToApiResult(IContextSource $context, ApiResult $result)
Append the debug info to given ApiResult.
Definition MWDebug.php:601
static $deprecationWarnings
Array of functions that have already been warned, formatted function-caller to prevent a buttload of ...
Definition MWDebug.php:68
static string[] $deprecationFilters
Deprecation filter regexes.
Definition MWDebug.php:73
static clearLog()
Clears internal log array and deprecation tracking.
Definition MWDebug.php:164
static sendRawDeprecated( $msg, $sendToLog=true, $callerFunc='')
Send a raw deprecation message to the log and the debug toolbar, without filtering of duplicate messa...
Definition MWDebug.php:322
static deinit()
Disable the debugger.
Definition MWDebug.php:114
static sendMessage( $msg, $group, $level)
Send a message to the debug log and optionally also trigger a PHP error, depending on the $level argu...
Definition MWDebug.php:428
static query( $sql, $function, $runTime, $dbhost)
Begins profiling on a database query.
Definition MWDebug.php:475
static getLog()
Returns internal log array.
Definition MWDebug.php:156
static log( $str)
Adds a line to the log.
Definition MWDebug.php:137
static getDebugInfo(IContextSource $context)
Returns the HTML to add to the page for the toolbar.
Definition MWDebug.php:635
static debugMsg( $str, $context=[])
This is a method to pass messages from wfDebug to the pretty debugger.
Definition MWDebug.php:444
static clearDeprecationFilters()
Clear all deprecation filters.
Definition MWDebug.php:368
static getCallerDescription( $callerOffset)
Get an array describing the calling function at a specified offset.
Definition MWDebug.php:379
static formatCallerDescription( $msg, $caller)
Append a caller description to an error message.
Definition MWDebug.php:416
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:225
static $query
SQL statements of the database queries.
Definition MWDebug.php:53
static getHTMLDebugLog()
Generate debug log in HTML for displaying at the bottom of the main content area.
Definition MWDebug.php:575
static filterDeprecationForTest( $regex)
Deprecation messages matching the supplied regex will be suppressed.
Definition MWDebug.php:358
static init()
Enabled the debugger and load resource module.
Definition MWDebug.php:105
static deprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
Definition MWDebug.php:257
static getDebugHTML(IContextSource $context)
Returns the HTML to add to the page for the toolbar.
Definition MWDebug.php:541
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.
static makeInlineScript( $script, $nonce=null)
Make an HTML script that runs given JS code after startup and base modules.
static makeConfigSetScript(array $configuration)
Return JS code which will set the MediaWiki configuration array to the given value.
Interface for objects which can provide a MediaWiki context on request.
$line
Definition mcc.php:119
$debug
Definition mcc.php:31
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42