MediaWiki REL1_37
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
221 public static function deprecated( $function, $version = false,
222 $component = false, $callerOffset = 2
223 ) {
224 if ( $version ) {
225 $component = $component ?: 'MediaWiki';
226 $msg = "Use of $function was deprecated in $component $version.";
227 } else {
228 $msg = "Use of $function is deprecated.";
229 }
230 self::deprecatedMsg( $msg, $version, $component, $callerOffset + 1 );
231 }
232
255 public static function detectDeprecatedOverride( $instance, $class, $method, $version = false,
256 $component = false, $callerOffset = 2
257 ) {
258 $reflectionMethod = new ReflectionMethod( $instance, $method );
259 $declaringClass = $reflectionMethod->getDeclaringClass()->getName();
260
261 if ( $declaringClass === $class ) {
262 // not overridden, nothing to do
263 return false;
264 }
265
266 if ( $version ) {
267 $component = $component ?: 'MediaWiki';
268 $msg = "$declaringClass overrides $method which was deprecated in $component $version.";
269 self::deprecatedMsg( $msg, $version, $component, $callerOffset + 1 );
270 }
271
272 return true;
273 }
274
303 public static function deprecatedMsg( $msg, $version = false,
304 $component = false, $callerOffset = 2
305 ) {
306 if ( $callerOffset === false ) {
307 $callerFunc = '';
308 $rawMsg = $msg;
309 } else {
310 $callerDescription = self::getCallerDescription( $callerOffset );
311 $callerFunc = $callerDescription['func'];
312 $rawMsg = self::formatCallerDescription( $msg, $callerDescription );
313 }
314
315 $sendToLog = true;
316
317 // Check to see if there already was a warning about this function
318 if ( isset( self::$deprecationWarnings[$msg][$callerFunc] ) ) {
319 return;
320 } elseif ( isset( self::$deprecationWarnings[$msg] ) ) {
321 if ( self::$enabled ) {
322 $sendToLog = false;
323 } else {
324 return;
325 }
326 }
327
328 self::$deprecationWarnings[$msg][$callerFunc] = true;
329
330 if ( $version ) {
332
333 $component = $component ?: 'MediaWiki';
334 if ( $wgDeprecationReleaseLimit && $component === 'MediaWiki' ) {
335 # Strip -* off the end of $version so that branches can use the
336 # format #.##-branchname to avoid issues if the branch is merged into
337 # a version of MediaWiki later than what it was branched from
338 $comparableVersion = preg_replace( '/-.*$/', '', $version );
339
340 # If the comparableVersion is larger than our release limit then
341 # skip the warning message for the deprecation
342 if ( version_compare( $wgDeprecationReleaseLimit, $comparableVersion, '<' ) ) {
343 $sendToLog = false;
344 }
345 }
346 }
347
348 self::sendRawDeprecated(
349 $rawMsg,
350 $sendToLog,
351 $callerFunc
352 );
353 }
354
367 public static function sendRawDeprecated( $msg, $sendToLog = true, $callerFunc = '' ) {
368 foreach ( self::$deprecationFilters as $filter ) {
369 if ( preg_match( $filter, $msg ) ) {
370 return;
371 }
372 }
373
374 if ( $sendToLog ) {
375 trigger_error( $msg, E_USER_DEPRECATED );
376 }
377
378 if ( self::$enabled ) {
379 $logMsg = htmlspecialchars( $msg ) .
380 Html::rawElement( 'div', [ 'class' => 'mw-debug-backtrace' ],
381 Html::element( 'span', [], 'Backtrace:' ) . wfBacktrace()
382 );
383
384 self::$log[] = [
385 'msg' => $logMsg,
386 'type' => 'deprecated',
387 'caller' => $callerFunc,
388 ];
389 }
390 }
391
398 public static function filterDeprecationForTest( $regex ) {
399 if ( !defined( 'MW_PHPUNIT_TEST' ) && !defined( 'MW_PARSER_TEST' ) ) {
400 throw new RuntimeException( __METHOD__ . ' can only be used in tests' );
401 }
402 self::$deprecationFilters[] = $regex;
403 }
404
408 public static function clearDeprecationFilters() {
409 self::$deprecationFilters = [];
410 }
411
419 private static function getCallerDescription( $callerOffset ) {
420 $callers = wfDebugBacktrace();
421
422 if ( isset( $callers[$callerOffset] ) ) {
423 $callerfile = $callers[$callerOffset];
424 if ( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ) {
425 $file = $callerfile['file'] . ' at line ' . $callerfile['line'];
426 } else {
427 $file = '(internal function)';
428 }
429 } else {
430 $file = '(unknown location)';
431 }
432
433 if ( isset( $callers[$callerOffset + 1] ) ) {
434 $callerfunc = $callers[$callerOffset + 1];
435 $func = '';
436 if ( isset( $callerfunc['class'] ) ) {
437 $func .= $callerfunc['class'] . '::';
438 }
439 if ( isset( $callerfunc['function'] ) ) {
440 $func .= $callerfunc['function'];
441 }
442 } else {
443 $func = 'unknown';
444 }
445
446 return [ 'file' => $file, 'func' => $func ];
447 }
448
456 private static function formatCallerDescription( $msg, $caller ) {
457 // When changing this, update the below parseCallerDescription() method as well.
458 return $msg . ' [Called from ' . $caller['func'] . ' in ' . $caller['file'] . ']';
459 }
460
473 public static function parseCallerDescription( $msg ) {
474 $match = null;
475 preg_match( '/(.*) \[Called from ([^ ]+) in ([^ ]+) at line (\d+)\]$/', $msg, $match );
476 if ( $match ) {
477 return [
478 'message' => "{$match[1]} [Called from {$match[2]}]",
479 'func' => $match[2],
480 'file' => $match[3],
481 'line' => $match[4],
482 ];
483 } else {
484 return null;
485 }
486 }
487
496 private static function sendMessage( $msg, $group, $level ) {
497 if ( $level !== false ) {
498 trigger_error( $msg, $level );
499 }
500
501 wfDebugLog( $group, $msg );
502 }
503
512 public static function debugMsg( $str, $context = [] ) {
514
515 if ( self::$enabled || $wgDebugComments || $wgShowDebug ) {
516 if ( $context ) {
517 $prefix = '';
518 if ( isset( $context['prefix'] ) ) {
519 $prefix = $context['prefix'];
520 } elseif ( isset( $context['channel'] ) && $context['channel'] !== 'wfDebug' ) {
521 $prefix = "[{$context['channel']}] ";
522 }
523 if ( isset( $context['seconds_elapsed'] ) && isset( $context['memory_used'] ) ) {
524 $prefix .= "{$context['seconds_elapsed']} {$context['memory_used']} ";
525 }
526 $str = LegacyLogger::interpolate( $str, $context );
527 $str = $prefix . $str;
528 }
529 self::$debug[] = rtrim( UtfNormal\Validator::cleanUp( $str ) );
530 }
531 }
532
543 public static function query( $sql, $function, $runTime, $dbhost ) {
544 if ( !self::$enabled ) {
545 return false;
546 }
547
548 // Replace invalid UTF-8 chars with a square UTF-8 character
549 // This prevents json_encode from erroring out due to binary SQL data
550 $sql = preg_replace(
551 '/(
552 [\xC0-\xC1] # Invalid UTF-8 Bytes
553 | [\xF5-\xFF] # Invalid UTF-8 Bytes
554 | \xE0[\x80-\x9F] # Overlong encoding of prior code point
555 | \xF0[\x80-\x8F] # Overlong encoding of prior code point
556 | [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start
557 | [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start
558 | [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start
559 | (?<=[\x0-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle
560 | (?<![\xC2-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF4]
561 | [\xF0-\xF4][\x80-\xBF]|[\xF0-\xF4][\x80-\xBF]{2})[\x80-\xBF] # Overlong Sequence
562 | (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF]) # Short 3 byte sequence
563 | (?<=[\xF0-\xF4])[\x80-\xBF](?![\x80-\xBF]{2}) # Short 4 byte sequence
564 | (?<=[\xF0-\xF4][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF]) # Short 4 byte sequence (2)
565 )/x',
566 '■',
567 $sql
568 );
569
570 // last check for invalid utf8
571 $sql = UtfNormal\Validator::cleanUp( $sql );
572
573 self::$query[] = [
574 'sql' => "$dbhost: $sql",
575 'function' => $function,
576 'time' => $runTime,
577 ];
578
579 return true;
580 }
581
588 protected static function getFilesIncluded( IContextSource $context ) {
589 $files = get_included_files();
590 $fileList = [];
591 foreach ( $files as $file ) {
592 $size = filesize( $file );
593 $fileList[] = [
594 'name' => $file,
595 'size' => $context->getLanguage()->formatSize( $size ),
596 ];
597 }
598
599 return $fileList;
600 }
601
609 public static function getDebugHTML( IContextSource $context ) {
610 global $wgDebugComments;
611
612 $html = '';
613
614 if ( self::$enabled ) {
615 self::log( 'MWDebug output complete' );
616 $debugInfo = self::getDebugInfo( $context );
617
618 // Cannot use OutputPage::addJsConfigVars because those are already outputted
619 // by the time this method is called.
621 ResourceLoader::makeConfigSetScript( [ 'debugInfo' => $debugInfo ] ),
622 $context->getOutput()->getCSP()->getNonce()
623 );
624 }
625
626 if ( $wgDebugComments ) {
627 $html .= "<!-- Debug output:\n" .
628 htmlspecialchars( implode( "\n", self::$debug ), ENT_NOQUOTES ) .
629 "\n\n-->";
630 }
631
632 return $html;
633 }
634
643 public static function getHTMLDebugLog() {
644 global $wgShowDebug;
645
646 if ( !$wgShowDebug ) {
647 return '';
648 }
649
650 $ret = "\n<hr />\n<strong>Debug data:</strong><ul id=\"mw-debug-html\">\n";
651
652 foreach ( self::$debug as $line ) {
653 $display = nl2br( htmlspecialchars( trim( $line ) ) );
654
655 $ret .= "<li><code>$display</code></li>\n";
656 }
657
658 $ret .= '</ul>' . "\n";
659
660 return Html::rawElement( 'div', [ 'id' => 'mw-html-debug-log' ], $ret );
661 }
662
669 public static function appendDebugInfoToApiResult( IContextSource $context, ApiResult $result ) {
670 if ( !self::$enabled ) {
671 return;
672 }
673
674 // output errors as debug info, when display_errors is on
675 // this is necessary for all non html output of the api, because that clears all errors first
676 $obContents = ob_get_contents();
677 if ( $obContents ) {
678 $obContentArray = explode( '<br />', $obContents );
679 foreach ( $obContentArray as $obContent ) {
680 if ( trim( $obContent ) ) {
681 self::debugMsg( Sanitizer::stripAllTags( $obContent ) );
682 }
683 }
684 }
685
686 self::log( 'MWDebug output complete' );
687 $debugInfo = self::getDebugInfo( $context );
688
689 ApiResult::setIndexedTagName( $debugInfo, 'debuginfo' );
690 ApiResult::setIndexedTagName( $debugInfo['log'], 'line' );
691 ApiResult::setIndexedTagName( $debugInfo['debugLog'], 'msg' );
692 ApiResult::setIndexedTagName( $debugInfo['queries'], 'query' );
693 ApiResult::setIndexedTagName( $debugInfo['includes'], 'queries' );
694 $result->addValue( null, 'debuginfo', $debugInfo );
695 }
696
703 public static function getDebugInfo( IContextSource $context ) {
704 if ( !self::$enabled ) {
705 return [];
706 }
707
708 $request = $context->getRequest();
709
710 $branch = GitInfo::currentBranch();
711 if ( GitInfo::isSHA1( $branch ) ) {
712 // If it's a detached HEAD, the SHA1 will already be
713 // included in the MW version, so don't show it.
714 $branch = false;
715 }
716
717 return [
718 'mwVersion' => MW_VERSION,
719 'phpEngine' => 'PHP',
720 'phpVersion' => PHP_VERSION,
721 'gitRevision' => GitInfo::headSHA1(),
722 'gitBranch' => $branch,
723 'gitViewUrl' => GitInfo::headViewUrl(),
724 'time' => $request->getElapsedTime(),
725 'log' => self::$log,
726 'debugLog' => self::$debug,
727 'queries' => self::$query,
728 'request' => [
729 'method' => $request->getMethod(),
730 'url' => $request->getRequestURL(),
731 'headers' => $request->getAllHeaders(),
732 'params' => $request->getValues(),
733 ],
734 'memory' => $context->getLanguage()->formatSize( memory_get_usage() ),
735 'memoryPeak' => $context->getLanguage()->formatSize( memory_get_peak_usage() ),
736 'includes' => self::getFilesIncluded( $context ),
737 ];
738 }
739}
$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:36
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:588
static bool $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 parseCallerDescription( $msg)
Append a caller description to an error message.
Definition MWDebug.php:473
static appendDebugInfoToApiResult(IContextSource $context, ApiResult $result)
Append the debug info to given ApiResult.
Definition MWDebug.php:669
static array $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:367
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:496
static query( $sql, $function, $runTime, $dbhost)
Begins profiling on a database query.
Definition MWDebug.php:543
static getLog()
Returns internal log array.
Definition MWDebug.php:156
static log( $str)
Adds a line to the log.
Definition MWDebug.php:137
static detectDeprecatedOverride( $instance, $class, $method, $version=false, $component=false, $callerOffset=2)
Show a warning if $method declared in $class is overridden in $instance.
Definition MWDebug.php:255
static getDebugInfo(IContextSource $context)
Returns the HTML to add to the page for the toolbar.
Definition MWDebug.php:703
static debugMsg( $str, $context=[])
This is a method to pass messages from wfDebug to the pretty debugger.
Definition MWDebug.php:512
static clearDeprecationFilters()
Clear all deprecation filters.
Definition MWDebug.php:408
static getCallerDescription( $callerOffset)
Get an array describing the calling function at a specified offset.
Definition MWDebug.php:419
static formatCallerDescription( $msg, $caller)
Append a caller description to an error message.
Definition MWDebug.php:456
static array $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:221
static array $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:643
static filterDeprecationForTest( $regex)
Deprecation messages matching the supplied regex will be suppressed.
Definition MWDebug.php:398
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:303
static getDebugHTML(IContextSource $context)
Returns the HTML to add to the page for the toolbar.
Definition MWDebug.php:609
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