MediaWiki 1.41.2
MWDebug.php
Go to the documentation of this file.
1<?php
27use Wikimedia\WrappedString;
28use Wikimedia\WrappedStringList;
29
41class MWDebug {
47 protected static $log = [];
48
54 protected static $debug = [];
55
61 protected static $query = [];
62
68 protected static $enabled = false;
69
76 protected static $deprecationWarnings = [];
77
81 protected static $deprecationFilters = [];
82
86 public static function setup() {
87 global $wgDebugToolbar,
89
90 if (
91 // Easy to forget to falsify $wgDebugToolbar for static caches.
92 // If file cache or CDN cache is on, just disable this (DWIMD).
93 $wgUseCdn ||
95 // Keep MWDebug off on CLI. This prevents MWDebug from eating up
96 // all the memory for logging SQL queries in maintenance scripts.
98 ) {
99 return;
100 }
101
102 if ( $wgDebugToolbar ) {
103 self::init();
104 }
105 }
106
113 public static function init() {
114 self::$enabled = true;
115 }
116
122 public static function deinit() {
123 self::$enabled = false;
124 }
125
133 public static function addModules( OutputPage $out ) {
134 if ( self::$enabled ) {
135 $out->addModules( 'mediawiki.debug' );
136 }
137 }
138
145 public static function log( $str ) {
146 if ( !self::$enabled ) {
147 return;
148 }
149 if ( !is_string( $str ) ) {
150 $str = print_r( $str, true );
151 }
152 self::$log[] = [
153 'msg' => htmlspecialchars( $str ),
154 'type' => 'log',
155 'caller' => wfGetCaller(),
156 ];
157 }
158
164 public static function getLog() {
165 return self::$log;
166 }
167
172 public static function clearLog() {
173 self::$log = [];
174 self::$deprecationWarnings = [];
175 }
176
188 public static function warning( $msg, $callerOffset = 1, $level = E_USER_NOTICE, $log = 'auto' ) {
190
191 if ( $log === 'auto' && !$wgDevelopmentWarnings ) {
192 $log = 'debug';
193 }
194
195 if ( $log === 'debug' ) {
196 $level = false;
197 }
198
199 $callerDescription = self::getCallerDescription( $callerOffset );
200
201 self::sendMessage(
202 self::formatCallerDescription( $msg, $callerDescription ),
203 'warning',
204 $level );
205
206 if ( self::$enabled ) {
207 self::$log[] = [
208 'msg' => htmlspecialchars( $msg ),
209 'type' => 'warn',
210 'caller' => $callerDescription['func'],
211 ];
212 }
213 }
214
229 public static function deprecated( $function, $version = false,
230 $component = false, $callerOffset = 2
231 ) {
232 if ( $version ) {
233 $component = $component ?: 'MediaWiki';
234 $msg = "Use of $function was deprecated in $component $version.";
235 } else {
236 $msg = "Use of $function is deprecated.";
237 }
238 self::deprecatedMsg( $msg, $version, $component, $callerOffset + 1 );
239 }
240
263 public static function detectDeprecatedOverride( $instance, $class, $method, $version = false,
264 $component = false, $callerOffset = 2
265 ) {
266 $reflectionMethod = new ReflectionMethod( $instance, $method );
267 $declaringClass = $reflectionMethod->getDeclaringClass()->getName();
268
269 if ( $declaringClass === $class ) {
270 // not overridden, nothing to do
271 return false;
272 }
273
274 if ( $version ) {
275 $component = $component ?: 'MediaWiki';
276 $msg = "$declaringClass overrides $method which was deprecated in $component $version.";
277 self::deprecatedMsg( $msg, $version, $component, $callerOffset + 1 );
278 }
279
280 return true;
281 }
282
311 public static function deprecatedMsg( $msg, $version = false,
312 $component = false, $callerOffset = 2
313 ) {
314 if ( $callerOffset === false ) {
315 $callerFunc = '';
316 $rawMsg = $msg;
317 } else {
318 $callerDescription = self::getCallerDescription( $callerOffset );
319 $callerFunc = $callerDescription['func'];
320 $rawMsg = self::formatCallerDescription( $msg, $callerDescription );
321 }
322
323 $sendToLog = true;
324
325 // Check to see if there already was a warning about this function
326 if ( isset( self::$deprecationWarnings[$msg][$callerFunc] ) ) {
327 return;
328 } elseif ( isset( self::$deprecationWarnings[$msg] ) ) {
329 if ( self::$enabled ) {
330 $sendToLog = false;
331 } else {
332 return;
333 }
334 }
335
336 self::$deprecationWarnings[$msg][$callerFunc] = true;
337
338 if ( $version ) {
340
341 $component = $component ?: 'MediaWiki';
342 if ( $wgDeprecationReleaseLimit && $component === 'MediaWiki' ) {
343 # Strip -* off the end of $version so that branches can use the
344 # format #.##-branchname to avoid issues if the branch is merged into
345 # a version of MediaWiki later than what it was branched from
346 $comparableVersion = preg_replace( '/-.*$/', '', $version );
347
348 # If the comparableVersion is larger than our release limit then
349 # skip the warning message for the deprecation
350 if ( version_compare( $wgDeprecationReleaseLimit, $comparableVersion, '<' ) ) {
351 $sendToLog = false;
352 }
353 }
354 }
355
356 self::sendRawDeprecated(
357 $rawMsg,
358 $sendToLog,
359 $callerFunc
360 );
361 }
362
375 public static function sendRawDeprecated( $msg, $sendToLog = true, $callerFunc = '' ) {
376 foreach ( self::$deprecationFilters as $filter => $callback ) {
377 if ( preg_match( $filter, $msg ) ) {
378 if ( is_callable( $callback ) ) {
379 $callback();
380 }
381 return;
382 }
383 }
384
385 if ( $sendToLog ) {
386 trigger_error( $msg, E_USER_DEPRECATED );
387 }
388
389 if ( self::$enabled ) {
390 $logMsg = htmlspecialchars( $msg ) .
391 Html::rawElement( 'div', [ 'class' => 'mw-debug-backtrace' ],
392 Html::element( 'span', [], 'Backtrace:' ) . wfBacktrace()
393 );
394
395 self::$log[] = [
396 'msg' => $logMsg,
397 'type' => 'deprecated',
398 'caller' => $callerFunc,
399 ];
400 }
401 }
402
410 public static function filterDeprecationForTest(
411 string $regex, ?callable $callback = null
412 ): void {
413 if ( !defined( 'MW_PHPUNIT_TEST' ) && !defined( 'MW_PARSER_TEST' ) ) {
414 throw new LogicException( __METHOD__ . ' can only be used in tests' );
415 }
416 self::$deprecationFilters[$regex] = $callback;
417 }
418
422 public static function clearDeprecationFilters() {
423 self::$deprecationFilters = [];
424 }
425
433 private static function getCallerDescription( $callerOffset ) {
434 $callers = wfDebugBacktrace();
435
436 if ( isset( $callers[$callerOffset] ) ) {
437 $callerfile = $callers[$callerOffset];
438 if ( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ) {
439 $file = $callerfile['file'] . ' at line ' . $callerfile['line'];
440 } else {
441 $file = '(internal function)';
442 }
443 } else {
444 $file = '(unknown location)';
445 }
446
447 if ( isset( $callers[$callerOffset + 1] ) ) {
448 $callerfunc = $callers[$callerOffset + 1];
449 $func = '';
450 if ( isset( $callerfunc['class'] ) ) {
451 $func .= $callerfunc['class'] . '::';
452 }
453 if ( isset( $callerfunc['function'] ) ) {
454 $func .= $callerfunc['function'];
455 }
456 } else {
457 $func = 'unknown';
458 }
459
460 return [ 'file' => $file, 'func' => $func ];
461 }
462
470 private static function formatCallerDescription( $msg, $caller ) {
471 // When changing this, update the below parseCallerDescription() method as well.
472 return $msg . ' [Called from ' . $caller['func'] . ' in ' . $caller['file'] . ']';
473 }
474
487 public static function parseCallerDescription( $msg ) {
488 $match = null;
489 preg_match( '/(.*) \[Called from ([^ ]+) in ([^ ]+) at line (\d+)\]$/', $msg, $match );
490 if ( $match ) {
491 return [
492 'message' => "{$match[1]} [Called from {$match[2]}]",
493 'func' => $match[2],
494 'file' => $match[3],
495 'line' => $match[4],
496 ];
497 } else {
498 return null;
499 }
500 }
501
510 private static function sendMessage( $msg, $group, $level ) {
511 if ( $level !== false ) {
512 trigger_error( $msg, $level );
513 }
514
515 wfDebugLog( $group, $msg );
516 }
517
526 public static function debugMsg( $str, $context = [] ) {
528
529 if ( self::$enabled || $wgDebugComments || $wgShowDebug ) {
530 if ( $context ) {
531 $prefix = '';
532 if ( isset( $context['prefix'] ) ) {
533 $prefix = $context['prefix'];
534 } elseif ( isset( $context['channel'] ) && $context['channel'] !== 'wfDebug' ) {
535 $prefix = "[{$context['channel']}] ";
536 }
537 if ( isset( $context['seconds_elapsed'] ) && isset( $context['memory_used'] ) ) {
538 $prefix .= "{$context['seconds_elapsed']} {$context['memory_used']} ";
539 }
540 $str = LegacyLogger::interpolate( $str, $context );
541 $str = $prefix . $str;
542 }
543 self::$debug[] = rtrim( UtfNormal\Validator::cleanUp( $str ) );
544 }
545 }
546
557 public static function query( $sql, $function, $runTime, $dbhost ) {
558 if ( !self::$enabled ) {
559 return false;
560 }
561
562 // Replace invalid UTF-8 chars with a square UTF-8 character
563 // This prevents json_encode from erroring out due to binary SQL data
564 $sql = preg_replace(
565 '/(
566 [\xC0-\xC1] # Invalid UTF-8 Bytes
567 | [\xF5-\xFF] # Invalid UTF-8 Bytes
568 | \xE0[\x80-\x9F] # Overlong encoding of prior code point
569 | \xF0[\x80-\x8F] # Overlong encoding of prior code point
570 | [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start
571 | [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start
572 | [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start
573 | (?<=[\x0-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle
574 | (?<![\xC2-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF4]
575 | [\xF0-\xF4][\x80-\xBF]|[\xF0-\xF4][\x80-\xBF]{2})[\x80-\xBF] # Overlong Sequence
576 | (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF]) # Short 3 byte sequence
577 | (?<=[\xF0-\xF4])[\x80-\xBF](?![\x80-\xBF]{2}) # Short 4 byte sequence
578 | (?<=[\xF0-\xF4][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF]) # Short 4 byte sequence (2)
579 )/x',
580 '■',
581 $sql
582 );
583
584 // last check for invalid utf8
585 $sql = UtfNormal\Validator::cleanUp( $sql );
586
587 self::$query[] = [
588 'sql' => "$dbhost: $sql",
589 'function' => $function,
590 'time' => $runTime,
591 ];
592
593 return true;
594 }
595
602 protected static function getFilesIncluded( IContextSource $context ) {
603 $files = get_included_files();
604 $fileList = [];
605 foreach ( $files as $file ) {
606 $size = filesize( $file );
607 $fileList[] = [
608 'name' => $file,
609 'size' => $context->getLanguage()->formatSize( $size ),
610 ];
611 }
612
613 return $fileList;
614 }
615
623 public static function getDebugHTML( IContextSource $context ) {
624 global $wgDebugComments;
625
626 $html = [];
627
628 if ( self::$enabled ) {
629 self::log( 'MWDebug output complete' );
630 $debugInfo = self::getDebugInfo( $context );
631
632 // Cannot use OutputPage::addJsConfigVars because those are already outputted
633 // by the time this method is called.
634 $html[] = ResourceLoader::makeInlineScript(
635 ResourceLoader::makeConfigSetScript( [ 'debugInfo' => $debugInfo ] )
636 );
637 }
638
639 if ( $wgDebugComments ) {
640 $html[] = '<!-- Debug output:';
641 foreach ( self::$debug as $line ) {
642 $html[] = htmlspecialchars( $line, ENT_NOQUOTES );
643 }
644 $html[] = '-->';
645 }
646
647 return WrappedString::join( "\n", $html );
648 }
649
658 public static function getHTMLDebugLog() {
659 global $wgShowDebug;
660
661 $html = [];
662 if ( $wgShowDebug ) {
663 $html[] = Html::openElement( 'div', [ 'id' => 'mw-html-debug-log' ] );
664 $html[] = "<hr />\n<strong>Debug data:</strong><ul id=\"mw-debug-html\">";
665
666 foreach ( self::$debug as $line ) {
667 $display = nl2br( htmlspecialchars( trim( $line ) ) );
668
669 $html[] = "<li><code>$display</code></li>";
670 }
671
672 $html[] = '</ul>';
673 $html[] = '</div>';
674 }
675 return WrappedString::join( "\n", $html );
676 }
677
684 public static function appendDebugInfoToApiResult( IContextSource $context, ApiResult $result ) {
685 if ( !self::$enabled ) {
686 return;
687 }
688
689 // output errors as debug info, when display_errors is on
690 // this is necessary for all non html output of the api, because that clears all errors first
691 $obContents = ob_get_contents();
692 if ( $obContents ) {
693 $obContentArray = explode( '<br />', $obContents );
694 foreach ( $obContentArray as $obContent ) {
695 if ( trim( $obContent ) ) {
696 self::debugMsg( Sanitizer::stripAllTags( $obContent ) );
697 }
698 }
699 }
700
701 self::log( 'MWDebug output complete' );
702 $debugInfo = self::getDebugInfo( $context );
703
704 ApiResult::setIndexedTagName( $debugInfo, 'debuginfo' );
705 ApiResult::setIndexedTagName( $debugInfo['log'], 'line' );
706 ApiResult::setIndexedTagName( $debugInfo['debugLog'], 'msg' );
707 ApiResult::setIndexedTagName( $debugInfo['queries'], 'query' );
708 ApiResult::setIndexedTagName( $debugInfo['includes'], 'queries' );
709 $result->addValue( null, 'debuginfo', $debugInfo );
710 }
711
718 public static function getDebugInfo( IContextSource $context ) {
719 if ( !self::$enabled ) {
720 return [];
721 }
722
723 $request = $context->getRequest();
724
725 $branch = GitInfo::currentBranch();
726 if ( GitInfo::isSHA1( $branch ) ) {
727 // If it's a detached HEAD, the SHA1 will already be
728 // included in the MW version, so don't show it.
729 $branch = false;
730 }
731
732 return [
733 'mwVersion' => MW_VERSION,
734 'phpEngine' => 'PHP',
735 'phpVersion' => PHP_VERSION,
736 'gitRevision' => GitInfo::headSHA1(),
737 'gitBranch' => $branch,
738 'gitViewUrl' => GitInfo::headViewUrl(),
739 'time' => $request->getElapsedTime(),
740 'log' => self::$log,
741 'debugLog' => self::$debug,
742 'queries' => self::$query,
743 'request' => [
744 'method' => $request->getMethod(),
745 'url' => $request->getRequestURL(),
746 'headers' => $request->getAllHeaders(),
747 'params' => $request->getValues(),
748 ],
749 'memory' => $context->getLanguage()->formatSize( memory_get_usage() ),
750 'memoryPeak' => $context->getLanguage()->formatSize( memory_get_peak_usage() ),
751 'includes' => self::getFilesIncluded( $context ),
752 ];
753 }
754}
const MW_VERSION
The running version of MediaWiki.
Definition Defines.php:36
if(MW_ENTRY_POINT==='index') 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().
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:88
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.
Debug toolbar.
Definition MWDebug.php:41
static warning( $msg, $callerOffset=1, $level=E_USER_NOTICE, $log='auto')
Adds a warning entry to the log.
Definition MWDebug.php:188
static getFilesIncluded(IContextSource $context)
Returns a list of files included, along with their size.
Definition MWDebug.php:602
static array $deprecationFilters
Keys are regexes, values are optional callbacks to call if the filter is hit.
Definition MWDebug.php:81
static bool $enabled
Is the debugger enabled?
Definition MWDebug.php:68
static setup()
Definition MWDebug.php:86
static filterDeprecationForTest(string $regex, ?callable $callback=null)
Deprecation messages matching the supplied regex will be suppressed.
Definition MWDebug.php:410
static addModules(OutputPage $out)
Add ResourceLoader modules to the OutputPage object if debugging is enabled.
Definition MWDebug.php:133
static parseCallerDescription( $msg)
Append a caller description to an error message.
Definition MWDebug.php:487
static appendDebugInfoToApiResult(IContextSource $context, ApiResult $result)
Append the debug info to given ApiResult.
Definition MWDebug.php:684
static array $deprecationWarnings
Array of functions that have already been warned, formatted function-caller to prevent a buttload of ...
Definition MWDebug.php:76
static clearLog()
Clears internal log array and deprecation tracking.
Definition MWDebug.php:172
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:375
static deinit()
Disable the debugger.
Definition MWDebug.php:122
static query( $sql, $function, $runTime, $dbhost)
Begins profiling on a database query.
Definition MWDebug.php:557
static getLog()
Returns internal log array.
Definition MWDebug.php:164
static array $debug
Debug messages from wfDebug().
Definition MWDebug.php:54
static log( $str)
Adds a line to the log.
Definition MWDebug.php:145
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:263
static getDebugInfo(IContextSource $context)
Returns the HTML to add to the page for the toolbar.
Definition MWDebug.php:718
static debugMsg( $str, $context=[])
This is a method to pass messages from wfDebug to the pretty debugger.
Definition MWDebug.php:526
static clearDeprecationFilters()
Clear all deprecation filters.
Definition MWDebug.php:422
static array $log
Log lines.
Definition MWDebug.php:47
static deprecated( $function, $version=false, $component=false, $callerOffset=2)
Show a warning that $function is deprecated.
Definition MWDebug.php:229
static array $query
SQL statements of the database queries.
Definition MWDebug.php:61
static getHTMLDebugLog()
Generate debug log in HTML for displaying at the bottom of the main content area.
Definition MWDebug.php:658
static init()
Enabled the debugger and load resource module.
Definition MWDebug.php:113
static deprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
Definition MWDebug.php:311
static getDebugHTML(IContextSource $context)
Returns the HTML to add to the page for the toolbar.
Definition MWDebug.php:623
This class is a collection of static functions that serve two purposes:
Definition Html.php:57
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.
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:46
ResourceLoader is a loading system for JavaScript and CSS resources.
$wgUseCdn
Config variable stub for the UseCdn setting, for use by phpdoc and IDEs.
$wgDeprecationReleaseLimit
Config variable stub for the DeprecationReleaseLimit setting, for use by phpdoc and IDEs.
$wgDebugComments
Config variable stub for the DebugComments setting, for use by phpdoc and IDEs.
$wgShowDebug
Config variable stub for the ShowDebug setting, for use by phpdoc and IDEs.
$wgDebugToolbar
Config variable stub for the DebugToolbar setting, for use by phpdoc and IDEs.
$wgDevelopmentWarnings
Config variable stub for the DevelopmentWarnings setting, for use by phpdoc and IDEs.
$wgUseFileCache
Config variable stub for the UseFileCache setting, for use by phpdoc and IDEs.
Interface for objects which can provide a MediaWiki context on request.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42