26 use Wikimedia\NormalizedException\INormalizedException;
36 public const CAUGHT_BY_HANDLER =
'mwe_handler';
38 public const CAUGHT_BY_ENTRYPOINT =
'entrypoint';
40 public const CAUGHT_BY_OTHER =
'other';
71 private static $logExceptionBacktrace =
true;
78 private static $propagateErrors;
88 bool $logExceptionBacktrace =
true,
89 bool $propagateErrors =
true
91 self::$logExceptionBacktrace = $logExceptionBacktrace;
92 self::$propagateErrors = $propagateErrors;
107 set_exception_handler(
'MWExceptionHandler::handleUncaughtException' );
111 set_error_handler(
'MWExceptionHandler::handleError' );
116 self::$reservedMemory = str_repeat(
' ', 16384 );
117 register_shutdown_function(
'MWExceptionHandler::handleFatalError' );
124 protected static function report( Throwable $e ) {
135 }
catch ( Throwable $e2 ) {
148 private static function rollbackPrimaryChanges() {
149 if ( !MediaWikiServices::hasInstance() ) {
155 $services = MediaWikiServices::getInstance();
156 if ( $services->isServiceDisabled(
'DBLoadBalancerFactory' ) ) {
167 $lbFactory = $services->getDBLoadBalancerFactory();
168 $lbFactory->rollbackPrimaryChanges( __METHOD__ );
169 $lbFactory->flushPrimarySessions( __METHOD__ );
191 $catcher = self::CAUGHT_BY_OTHER
193 self::rollbackPrimaryChanges();
205 $catcher = self::CAUGHT_BY_OTHER
222 register_shutdown_function(
248 public static function handleException( Throwable $e, $catcher = self::CAUGHT_BY_OTHER ) {
291 case E_COMPILE_WARNING:
292 $prefix =
'PHP Warning: ';
293 $severity = LogLevel::ERROR;
296 $prefix =
'PHP Notice: ';
297 $severity = LogLevel::ERROR;
301 $prefix =
'PHP Notice: ';
302 $severity = LogLevel::WARNING;
306 $prefix =
'PHP Warning: ';
307 $severity = LogLevel::WARNING;
310 $prefix =
'PHP Strict Standards: ';
311 $severity = LogLevel::WARNING;
314 $prefix =
'PHP Deprecated: ';
315 $severity = LogLevel::WARNING;
317 case E_USER_DEPRECATED:
318 $prefix =
'PHP Deprecated: ';
319 $severity = LogLevel::WARNING;
325 $file = $real[
'file'];
326 $line = $real[
'line'];
327 $message = $real[
'message'];
331 $prefix =
'PHP Unknown error: ';
332 $severity = LogLevel::ERROR;
337 $e =
new ErrorException( $prefix . $message, 0, $level,
$file, $line );
338 self::logError( $e,
'error', $severity, self::CAUGHT_BY_HANDLER );
343 return !( self::$propagateErrors || ini_get(
'track_errors' ) );
363 self::$reservedMemory =
null;
365 $lastError = error_get_last();
366 if ( $lastError ===
null ) {
370 $level = $lastError[
'type'];
371 $message = $lastError[
'message'];
372 $file = $lastError[
'file'];
373 $line = $lastError[
'line'];
375 if ( !in_array( $level, self::$fatalErrorTypes ) ) {
382 '[{reqId}] {exception_url} PHP Fatal Error',
383 ( $line ||
$file ) ?
' from' :
'',
384 $line ?
" line $line" :
'',
385 ( $line &&
$file ) ?
' of' :
'',
386 $file ?
" $file" :
'',
389 $msg = implode(
'', $msgParts );
392 if ( preg_match(
"/Class '\w+' not found/", $message ) ) {
397 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.
399 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.
404 $e =
new ErrorException(
"PHP Fatal Error: {$message}", 0, $level,
$file, $line );
405 $logger = LoggerFactory::getInstance(
'exception' );
406 $logger->error( $msg, self::getLogContext( $e, self::CAUGHT_BY_HANDLER ) );
422 $from =
'from ' . $e->getFile() .
'(' . $e->getLine() .
')' .
"\n";
438 foreach ( $trace as $level => $frame ) {
439 if ( isset( $frame[
'file'] ) && isset( $frame[
'line'] ) ) {
440 $text .=
"{$pad}#{$level} {$frame['file']}({$frame['line']}): ";
446 $text .=
"{$pad}#{$level} [internal function]: ";
449 if ( isset( $frame[
'class'] ) && isset( $frame[
'type'] ) && isset( $frame[
'function'] ) ) {
450 $text .= $frame[
'class'] . $frame[
'type'] . $frame[
'function'];
452 $text .= $frame[
'function'] ??
'NO_FUNCTION_GIVEN';
455 if ( isset( $frame[
'args'] ) ) {
456 $text .=
'(' . implode(
', ', $frame[
'args'] ) .
")\n";
463 $text .=
"{$pad}#{$level} {main}";
480 return static::redactTrace( $e->getTrace() );
494 return array_map(
static function ( $frame ) {
495 if ( isset( $frame[
'args'] ) ) {
496 $frame[
'args'] = array_map(
static function ( $arg ) {
497 return is_object( $arg ) ? get_class( $arg ) : gettype( $arg );
532 $type = get_class( $e );
533 $message = $e->getMessage();
537 $message =
"A database query error has occurred. Did you forget to run"
538 .
" your application's database schema updater after upgrading"
539 .
" or after adding a new extension?\n\nPlease see"
540 .
" https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Upgrading and"
541 .
" https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:How_to_debug"
542 .
" for more information.\n\n"
546 return "[$id] $url $type: $message";
559 if ( $e instanceof INormalizedException ) {
560 $message = $e->getNormalizedMessage();
562 $message = $e->getMessage();
564 if ( !$e instanceof ErrorException ) {
569 $message = get_class( $e ) .
": $message";
572 return "[{reqId}] {exception_url} $message";
581 $type = get_class( $e );
582 return '[' . $reqId .
'] '
583 . gmdate(
'Y-m-d H:i:s' ) .
': '
584 .
'Fatal exception of type "' .
$type .
'"';
599 public static function getLogContext( Throwable $e, $catcher = self::CAUGHT_BY_OTHER ) {
610 'caught_by' => $catcher
612 if ( $e instanceof INormalizedException ) {
613 $context += $e->getMessageContext();
632 $catcher = self::CAUGHT_BY_OTHER
636 'type' => get_class( $e ),
637 'file' => $e->getFile(),
638 'line' => $e->getLine(),
639 'message' => $e->getMessage(),
640 'code' => $e->getCode(),
642 'caught_by' => $catcher
645 if ( $e instanceof ErrorException &&
646 ( error_reporting() & $e->getSeverity() ) === 0
649 $data[
'suppressed'] =
true;
652 if ( self::$logExceptionBacktrace ) {
656 $previous = $e->getPrevious();
657 if ( $previous !==
null ) {
722 $catcher = self::CAUGHT_BY_OTHER
725 self::getStructuredExceptionData( $e, $catcher ),
744 $catcher = self::CAUGHT_BY_OTHER,
747 if ( !( $e instanceof
MWException ) || $e->isLoggable() ) {
748 $logger = LoggerFactory::getInstance(
'exception' );
751 $context[
'extraData'] = $extraData;
754 self::getLogNormalMessage( $e ),
759 if ( $json !==
false ) {
760 $logger = LoggerFactory::getInstance(
'exception-json' );
761 $logger->error( $json, [
'private' =>
true ] );
764 (
new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )->onLogException( $e,
false );
776 private static function logError(
785 $suppressed = ( error_reporting() & $e->getSeverity() ) === 0;
786 if ( !$suppressed ) {
787 $logger = LoggerFactory::getInstance( $channel );
790 self::getLogNormalMessage( $e ),
791 self::getLogContext( $e, $catcher )
797 if ( $json !==
false ) {
798 $logger = LoggerFactory::getInstance(
"{$channel}-json" );
805 $unfilteredLevel = $suppressed ? LogLevel::DEBUG : $level;
806 $logger->log( $unfilteredLevel, $json, [
'private' =>
true ] );
809 (
new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )->onLogException( $e, $suppressed );
wfIsCLI()
Check if we are running from the commandline.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
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.