17use Wikimedia\NormalizedException\INormalizedException;
21use Wikimedia\Services\RecursiveServiceDependencyException;
46 private const FATAL_ERROR_TYPES = [
62 private static $logExceptionBacktrace =
true;
69 private static $propagateErrors;
79 bool $logExceptionBacktrace =
true,
80 bool $propagateErrors =
true
82 self::$logExceptionBacktrace = $logExceptionBacktrace;
83 self::$propagateErrors = $propagateErrors;
98 set_exception_handler( self::handleUncaughtException( ... ) );
102 set_error_handler( self::handleError( ... ) );
107 self::$reservedMemory = str_repeat(
' ', 16384 );
108 register_shutdown_function( self::handleFatalError( ... ) );
114 protected static function report( Throwable $e ) {
117 if ( $e instanceof
MWException && $e->hasOverriddenHandler() ) {
125 }
catch ( Throwable $e2 ) {
138 private static function rollbackPrimaryChanges() {
146 $lbFactory = $services->peekService(
'DBLoadBalancerFactory' );
147 '@phan-var LBFactory $lbFactory';
158 $lbFactory->rollbackPrimaryChanges( __METHOD__ );
159 $lbFactory->flushPrimarySessions( __METHOD__ );
160 }
catch ( DBError $e ) {
181 $catcher = self::CAUGHT_BY_OTHER
183 self::rollbackPrimaryChanges();
199 register_shutdown_function(
200 static function (): never {
222 public static function handleException( Throwable $e, $catcher = self::CAUGHT_BY_OTHER ) {
249 if ( defined(
'E_STRICT' ) && $level == @constant(
'E_STRICT' ) ) {
250 $level = E_USER_NOTICE;
271 case E_COMPILE_WARNING:
272 $prefix =
'PHP Warning: ';
273 $severity = LogLevel::ERROR;
276 $prefix =
'PHP Notice: ';
277 $severity = LogLevel::ERROR;
281 $prefix =
'PHP Notice: ';
282 $severity = LogLevel::WARNING;
286 $prefix =
'PHP Warning: ';
287 $severity = LogLevel::WARNING;
290 $prefix =
'PHP Deprecated: ';
291 $severity = LogLevel::WARNING;
293 case E_USER_DEPRECATED:
294 $prefix =
'PHP Deprecated: ';
295 $severity = LogLevel::WARNING;
296 $real = MWDebug::parseCallerDescription( $message );
301 $file = $real[
'file'];
302 $line = $real[
'line'];
303 $message = $real[
'message'];
307 $prefix =
'PHP Unknown error: ';
308 $severity = LogLevel::ERROR;
313 $e =
new ErrorException( $prefix . $message, 0, $level, $file, $line );
314 self::logError( $e, $severity, self::CAUGHT_BY_HANDLER );
317 return !self::$propagateErrors;
338 $lastError = error_get_last();
339 if ( $lastError ===
null ) {
343 $level = $lastError[
'type'];
344 $message = $lastError[
'message'];
345 $file = $lastError[
'file'];
346 $line = $lastError[
'line'];
348 if ( !in_array( $level, self::FATAL_ERROR_TYPES ) ) {
355 '[{reqId}] {exception_url} PHP Fatal Error',
356 ( $line || $file ) ?
' from' :
'',
357 $line ?
" line $line" :
'',
358 ( $line && $file ) ?
' of' :
'',
359 $file ?
" $file" :
'',
362 $msg = implode(
'', $msgParts );
365 if ( preg_match(
"/Class '\w+' not found/", $message ) ) {
370MediaWiki or an installed extension
requires this class but it is not embedded directly in
MediaWiki's git repository and must be installed separately by the end user.
372Please see <a href=
"https://www.mediawiki.org/wiki/Download_from_Git#Fetch_external_libraries">mediawiki.org</a>
for help on installing the required components.
377 $e =
new ErrorException(
"PHP Fatal Error: {$message}", 0, $level, $file, $line );
378 $logger = LoggerFactory::getInstance(
'exception' );
379 $logger->error( $msg, self::getLogContext( $e, self::CAUGHT_BY_HANDLER ) );
393 $from =
'from ' . $e->getFile() .
'(' . $e->getLine() .
')' .
"\n";
394 return $from . self::prettyPrintTrace( self::getRedactedTrace( $e ) );
409 foreach ( $trace as $level => $frame ) {
410 if ( isset( $frame[
'file'] ) && isset( $frame[
'line'] ) ) {
411 $text .=
"{$pad}#{$level} {$frame['file']}({$frame['line']}): ";
417 $text .=
"{$pad}#{$level} [internal function]: ";
420 if ( isset( $frame[
'class'] ) && isset( $frame[
'type'] ) && isset( $frame[
'function'] ) ) {
421 $text .= $frame[
'class'] . $frame[
'type'] . $frame[
'function'];
423 $text .= $frame[
'function'] ??
'NO_FUNCTION_GIVEN';
426 if ( isset( $frame[
'args'] ) ) {
427 $text .=
'(' . implode(
', ', $frame[
'args'] ) .
")\n";
434 $text .=
"{$pad}#{$level} {main}";
451 return static::redactTrace( $e->getTrace() );
465 return array_map(
static function ( $frame ) {
466 if ( isset( $frame[
'args'] ) ) {
467 $frame[
'args'] = array_map(
'get_debug_type', $frame[
'args'] );
484 return WebRequest::getGlobalRequestURL();
498 $id = WebRequest::getRequestId();
499 $type = get_class( $e );
500 $message = $e->getMessage();
501 $url = self::getURL() ?:
'[no req]';
504 $message =
"A database query error has occurred. Did you forget to run"
505 .
" your application's database schema updater after upgrading"
506 .
" or after adding a new extension?\n\nPlease see"
507 .
" https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Upgrading and"
508 .
" https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:How_to_debug"
509 .
" for more information.\n\n"
513 return "[$id] $url $type: $message";
526 if ( $e instanceof INormalizedException ) {
527 $message = $e->getNormalizedMessage();
529 $message = $e->getMessage();
531 if ( !$e instanceof ErrorException ) {
536 $message = get_class( $e ) .
": $message";
539 return "[{reqId}] {exception_url} $message";
547 $reqId = WebRequest::getRequestId();
548 $type = get_class( $e );
549 return '[' . $reqId .
'] '
550 . gmdate(
'Y-m-d H:i:s' ) .
': '
551 .
'Fatal exception of type "' . $type .
'"';
566 public static function getLogContext( Throwable $e, $catcher = self::CAUGHT_BY_OTHER ) {
569 'exception_url' => self::getURL() ?:
'[no req]',
576 'reqId' => WebRequest::getRequestId(),
577 'caught_by' => $catcher
579 if ( $e instanceof INormalizedException ) {
580 $context += $e->getMessageContext();
599 $catcher = self::CAUGHT_BY_OTHER
602 'id' => WebRequest::getRequestId(),
603 'type' => get_class( $e ),
604 'file' => $e->getFile(),
605 'line' => $e->getLine(),
606 'message' => $e->getMessage(),
607 'code' => $e->getCode(),
608 'url' => self::getURL() ?:
null,
609 'caught_by' => $catcher
612 if ( $e instanceof ErrorException &&
613 ( error_reporting() & $e->getSeverity() ) === 0
616 $data[
'suppressed'] =
true;
619 if ( self::$logExceptionBacktrace ) {
620 $data[
'backtrace'] = self::getRedactedTrace( $e );
623 $previous = $e->getPrevious();
624 if ( $previous !==
null ) {
625 $data[
'previous'] = self::getStructuredExceptionData( $previous, $catcher );
644 $catcher = self::CAUGHT_BY_OTHER,
647 if ( !( $e instanceof
MWException ) || $e->isLoggable() ) {
648 $logger = LoggerFactory::getInstance(
'exception' );
649 $context = self::getLogContext( $e, $catcher );
651 $context[
'extraData'] = $extraData;
654 self::getLogNormalMessage( $e ),
658 self::callLogExceptionHook( $e,
false );
669 private static function logError(
675 $suppressed = ( error_reporting() & $e->getSeverity() ) === 0;
682 $channel =
'silenced-error';
683 $level = LogLevel::DEBUG;
687 $logger = LoggerFactory::getInstance( $channel );
690 self::getLogNormalMessage( $e ),
691 self::getLogContext( $e, $catcher )
694 self::callLogExceptionHook( $e, $suppressed );
703 private static function callLogExceptionHook( Throwable $e,
bool $suppressed ) {
717 ->onLogException( $e, $suppressed );
718 }
catch ( RecursiveServiceDependencyException ) {
725class_alias( MWExceptionHandler::class,
'MWExceptionHandler' );
wfIsCLI()
Check if we are running from the commandline.