25 use Wikimedia\NormalizedException\INormalizedException;
35 public const CAUGHT_BY_HANDLER =
'mwe_handler';
37 public const CAUGHT_BY_ENTRYPOINT =
'entrypoint';
39 public const CAUGHT_BY_OTHER =
'other';
70 private static $logExceptionBacktrace =
true;
77 private static $propagateErrors;
87 bool $logExceptionBacktrace =
true,
88 bool $propagateErrors =
true
90 self::$logExceptionBacktrace = $logExceptionBacktrace;
91 self::$propagateErrors = $propagateErrors;
106 set_exception_handler(
'MWExceptionHandler::handleUncaughtException' );
110 set_error_handler(
'MWExceptionHandler::handleError' );
115 self::$reservedMemory = str_repeat(
' ', 16384 );
116 register_shutdown_function(
'MWExceptionHandler::handleFatalError' );
123 protected static function report( Throwable $e ) {
134 }
catch ( Throwable $e2 ) {
147 private static function rollbackPrimaryChanges() {
148 if ( !MediaWikiServices::hasInstance() ) {
154 $services = MediaWikiServices::getInstance();
155 if ( $services->isServiceDisabled(
'DBLoadBalancerFactory' ) ) {
166 $lbFactory = $services->getDBLoadBalancerFactory();
167 $lbFactory->rollbackPrimaryChanges( __METHOD__ );
168 $lbFactory->flushPrimarySessions( __METHOD__ );
190 $catcher = self::CAUGHT_BY_OTHER
192 self::rollbackPrimaryChanges();
204 $catcher = self::CAUGHT_BY_OTHER
221 register_shutdown_function(
247 public static function handleException( Throwable $e, $catcher = self::CAUGHT_BY_OTHER ) {
290 case E_COMPILE_WARNING:
291 $prefix =
'PHP Warning: ';
292 $severity = LogLevel::ERROR;
295 $prefix =
'PHP Notice: ';
296 $severity = LogLevel::ERROR;
300 $prefix =
'PHP Notice: ';
301 $severity = LogLevel::WARNING;
305 $prefix =
'PHP Warning: ';
306 $severity = LogLevel::WARNING;
309 $prefix =
'PHP Strict Standards: ';
310 $severity = LogLevel::WARNING;
313 $prefix =
'PHP Deprecated: ';
314 $severity = LogLevel::WARNING;
316 case E_USER_DEPRECATED:
317 $prefix =
'PHP Deprecated: ';
318 $severity = LogLevel::WARNING;
324 $file = $real[
'file'];
325 $line = $real[
'line'];
326 $message = $real[
'message'];
330 $prefix =
'PHP Unknown error: ';
331 $severity = LogLevel::ERROR;
336 $e =
new ErrorException( $prefix . $message, 0, $level,
$file, $line );
337 self::logError( $e,
'error', $severity, self::CAUGHT_BY_HANDLER );
342 return !( self::$propagateErrors || ini_get(
'track_errors' ) );
362 self::$reservedMemory =
null;
364 $lastError = error_get_last();
365 if ( $lastError ===
null ) {
369 $level = $lastError[
'type'];
370 $message = $lastError[
'message'];
371 $file = $lastError[
'file'];
372 $line = $lastError[
'line'];
374 if ( !in_array( $level, self::$fatalErrorTypes ) ) {
381 '[{reqId}] {exception_url} PHP Fatal Error',
382 ( $line ||
$file ) ?
' from' :
'',
383 $line ?
" line $line" :
'',
384 ( $line &&
$file ) ?
' of' :
'',
385 $file ?
" $file" :
'',
388 $msg = implode(
'', $msgParts );
391 if ( preg_match(
"/Class '\w+' not found/", $message ) ) {
396 MediaWiki 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.
398 Please see <a href=
"https://www.mediawiki.org/wiki/Download_from_Git#Fetch_external_libraries">mediawiki.org</a>
for help on installing the required components.
403 $e =
new ErrorException(
"PHP Fatal Error: {$message}", 0, $level,
$file, $line );
404 $logger = LoggerFactory::getInstance(
'exception' );
405 $logger->error( $msg, self::getLogContext( $e, self::CAUGHT_BY_HANDLER ) );
421 $from =
'from ' . $e->getFile() .
'(' . $e->getLine() .
')' .
"\n";
437 foreach ( $trace as $level => $frame ) {
438 if ( isset( $frame[
'file'] ) && isset( $frame[
'line'] ) ) {
439 $text .=
"{$pad}#{$level} {$frame['file']}({$frame['line']}): ";
445 $text .=
"{$pad}#{$level} [internal function]: ";
448 if ( isset( $frame[
'class'] ) && isset( $frame[
'type'] ) && isset( $frame[
'function'] ) ) {
449 $text .= $frame[
'class'] . $frame[
'type'] . $frame[
'function'];
451 $text .= $frame[
'function'] ??
'NO_FUNCTION_GIVEN';
454 if ( isset( $frame[
'args'] ) ) {
455 $text .=
'(' . implode(
', ', $frame[
'args'] ) .
")\n";
462 $text .=
"{$pad}#{$level} {main}";
479 return static::redactTrace( $e->getTrace() );
493 return array_map(
static function ( $frame ) {
494 if ( isset( $frame[
'args'] ) ) {
495 $frame[
'args'] = array_map(
static function ( $arg ) {
496 return is_object( $arg ) ? get_class( $arg ) : gettype( $arg );
531 $type = get_class( $e );
532 $message = $e->getMessage();
536 $message =
"A database query error has occurred. Did you forget to run"
537 .
" your application's database schema updater after upgrading"
538 .
" or after adding a new extension?\n\nPlease see"
539 .
" https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Upgrading and"
540 .
" https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:How_to_debug"
541 .
" for more information.\n\n"
545 return "[$id] $url $type: $message";
558 if ( $e instanceof INormalizedException ) {
559 $message = $e->getNormalizedMessage();
561 $message = $e->getMessage();
563 if ( !$e instanceof ErrorException ) {
568 $message = get_class( $e ) .
": $message";
571 return "[{reqId}] {exception_url} $message";
580 $type = get_class( $e );
581 return '[' . $reqId .
'] '
582 . gmdate(
'Y-m-d H:i:s' ) .
': '
583 .
'Fatal exception of type "' .
$type .
'"';
598 public static function getLogContext( Throwable $e, $catcher = self::CAUGHT_BY_OTHER ) {
609 'caught_by' => $catcher
611 if ( $e instanceof INormalizedException ) {
612 $context += $e->getMessageContext();
631 $catcher = self::CAUGHT_BY_OTHER
635 'type' => get_class( $e ),
636 'file' => $e->getFile(),
637 'line' => $e->getLine(),
638 'message' => $e->getMessage(),
639 'code' => $e->getCode(),
641 'caught_by' => $catcher
644 if ( $e instanceof ErrorException &&
645 ( error_reporting() & $e->getSeverity() ) === 0
648 $data[
'suppressed'] =
true;
651 if ( self::$logExceptionBacktrace ) {
655 $previous = $e->getPrevious();
656 if ( $previous !==
null ) {
721 $catcher = self::CAUGHT_BY_OTHER
724 self::getStructuredExceptionData( $e, $catcher ),
743 $catcher = self::CAUGHT_BY_OTHER,
746 if ( !( $e instanceof
MWException ) || $e->isLoggable() ) {
747 $logger = LoggerFactory::getInstance(
'exception' );
750 $context[
'extraData'] = $extraData;
753 self::getLogNormalMessage( $e ),
758 if ( $json !==
false ) {
759 $logger = LoggerFactory::getInstance(
'exception-json' );
760 $logger->error( $json, [
'private' =>
true ] );
775 private static function logError(
784 $suppressed = ( error_reporting() & $e->getSeverity() ) === 0;
785 if ( !$suppressed ) {
786 $logger = LoggerFactory::getInstance( $channel );
789 self::getLogNormalMessage( $e ),
790 self::getLogContext( $e, $catcher )
796 if ( $json !==
false ) {
797 $logger = LoggerFactory::getInstance(
"{$channel}-json" );
804 $unfilteredLevel = $suppressed ? LogLevel::DEBUG : $level;
805 $logger->log( $unfilteredLevel, $json, [
'private' =>
true ] );
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
wfIsCLI()
Check if we are running from the commandline.
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
static parseCallerDescription( $msg)
Append a caller description to an error message.
Handler class for MWExceptions.
static getLogContext(Throwable $e, $catcher=self::CAUGHT_BY_OTHER)
Get a PSR-3 log event context from a Throwable.
static handleError( $level, $message, $file=null, $line=null)
Handler for set_error_handler() callback notifications.
static rollbackPrimaryChangesAndLog(Throwable $e, $catcher=self::CAUGHT_BY_OTHER)
Roll back any open database transactions and log the stack trace of the throwable.
static installHandler(bool $logExceptionBacktrace=true, bool $propagateErrors=true)
Install handlers with PHP.
static getStructuredExceptionData(Throwable $e, $catcher=self::CAUGHT_BY_OTHER)
Get a structured representation of a Throwable.
static rollbackMasterChangesAndLog(Throwable $e, $catcher=self::CAUGHT_BY_OTHER)
static getRedactedTraceAsString(Throwable $e)
Generate a string representation of a throwable's stack trace.
static report(Throwable $e)
Report a throwable to the user.
static logException(Throwable $e, $catcher=self::CAUGHT_BY_OTHER, $extraData=[])
Log a throwable to the exception log (if enabled).
static array $fatalErrorTypes
Error types that, if unhandled, are fatal to the request.
static getPublicLogMessage(Throwable $e)
static getRedactedTrace(Throwable $e)
Return a copy of a throwable's backtrace as an array.
static handleUncaughtException(Throwable $e)
Callback to use with PHP's set_exception_handler.
static prettyPrintTrace(array $trace, $pad='')
Generate a string representation of a stacktrace.
static string null $reservedMemory
static jsonSerializeException(Throwable $e, $pretty=false, $escaping=0, $catcher=self::CAUGHT_BY_OTHER)
Serialize a Throwable object to JSON.
static getLogMessage(Throwable $e)
Get a message formatting the throwable message and its origin.
static redactTrace(array $trace)
Redact a stacktrace generated by Throwable::getTrace(), debug_backtrace() or similar means.
static handleFatalError()
Callback used as a registered shutdown function.
static getLogNormalMessage(Throwable $e)
Get a normalised message for formatting with PSR-3 log event context.
static getURL()
If the exception occurred in the course of responding to a request, returns the requested URL.
static handleException(Throwable $e, $catcher=self::CAUGHT_BY_OTHER)
Exception handler which simulates the appropriate catch() handling:
static output(Throwable $e, $mode, Throwable $eNew=null)
static getRequestId()
Get the current request ID.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.