27 use Wikimedia\NormalizedException\INormalizedException;
37 public const CAUGHT_BY_HANDLER =
'mwe_handler';
39 public const CAUGHT_BY_ENTRYPOINT =
'entrypoint';
41 public const CAUGHT_BY_OTHER =
'other';
72 private static $logExceptionBacktrace =
true;
79 private static $propagateErrors;
89 bool $logExceptionBacktrace =
true,
90 bool $propagateErrors =
true
92 self::$logExceptionBacktrace = $logExceptionBacktrace;
93 self::$propagateErrors = $propagateErrors;
108 set_exception_handler( [ self::class,
'handleUncaughtException' ] );
112 set_error_handler( [ self::class,
'handleError' ] );
117 self::$reservedMemory = str_repeat(
' ', 16384 );
118 register_shutdown_function( [ self::class,
'handleFatalError' ] );
125 protected static function report( Throwable $e ) {
136 }
catch ( Throwable $e2 ) {
149 private static function rollbackPrimaryChanges() {
150 if ( !MediaWikiServices::hasInstance() ) {
156 $services = MediaWikiServices::getInstance();
157 if ( $services->isServiceDisabled(
'DBLoadBalancerFactory' ) ) {
168 $lbFactory = $services->getDBLoadBalancerFactory();
169 $lbFactory->rollbackPrimaryChanges( __METHOD__ );
170 $lbFactory->flushPrimarySessions( __METHOD__ );
192 $catcher = self::CAUGHT_BY_OTHER
194 self::rollbackPrimaryChanges();
206 $catcher = self::CAUGHT_BY_OTHER
223 register_shutdown_function(
249 public static function handleException( Throwable $e, $catcher = self::CAUGHT_BY_OTHER ) {
292 case E_COMPILE_WARNING:
293 $prefix =
'PHP Warning: ';
294 $severity = LogLevel::ERROR;
297 $prefix =
'PHP Notice: ';
298 $severity = LogLevel::ERROR;
302 $prefix =
'PHP Notice: ';
303 $severity = LogLevel::WARNING;
307 $prefix =
'PHP Warning: ';
308 $severity = LogLevel::WARNING;
311 $prefix =
'PHP Strict Standards: ';
312 $severity = LogLevel::WARNING;
315 $prefix =
'PHP Deprecated: ';
316 $severity = LogLevel::WARNING;
318 case E_USER_DEPRECATED:
319 $prefix =
'PHP Deprecated: ';
320 $severity = LogLevel::WARNING;
326 $file = $real[
'file'];
327 $line = $real[
'line'];
328 $message = $real[
'message'];
332 $prefix =
'PHP Unknown error: ';
333 $severity = LogLevel::ERROR;
338 $e =
new ErrorException( $prefix . $message, 0, $level,
$file, $line );
339 self::logError( $e,
'error', $severity, self::CAUGHT_BY_HANDLER );
344 return !( self::$propagateErrors || ini_get(
'track_errors' ) );
364 self::$reservedMemory =
null;
366 $lastError = error_get_last();
367 if ( $lastError ===
null ) {
371 $level = $lastError[
'type'];
372 $message = $lastError[
'message'];
373 $file = $lastError[
'file'];
374 $line = $lastError[
'line'];
376 if ( !in_array( $level, self::$fatalErrorTypes ) ) {
383 '[{reqId}] {exception_url} PHP Fatal Error',
384 ( $line ||
$file ) ?
' from' :
'',
385 $line ?
" line $line" :
'',
386 ( $line &&
$file ) ?
' of' :
'',
387 $file ?
" $file" :
'',
390 $msg = implode(
'', $msgParts );
393 if ( preg_match(
"/Class '\w+' not found/", $message ) ) {
398 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.
400 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.
405 $e =
new ErrorException(
"PHP Fatal Error: {$message}", 0, $level,
$file, $line );
406 $logger = LoggerFactory::getInstance(
'exception' );
407 $logger->error( $msg, self::getLogContext( $e, self::CAUGHT_BY_HANDLER ) );
423 $from =
'from ' . $e->getFile() .
'(' . $e->getLine() .
')' .
"\n";
439 foreach ( $trace as $level => $frame ) {
440 if ( isset( $frame[
'file'] ) && isset( $frame[
'line'] ) ) {
441 $text .=
"{$pad}#{$level} {$frame['file']}({$frame['line']}): ";
447 $text .=
"{$pad}#{$level} [internal function]: ";
450 if ( isset( $frame[
'class'] ) && isset( $frame[
'type'] ) && isset( $frame[
'function'] ) ) {
451 $text .= $frame[
'class'] . $frame[
'type'] . $frame[
'function'];
453 $text .= $frame[
'function'] ??
'NO_FUNCTION_GIVEN';
456 if ( isset( $frame[
'args'] ) ) {
457 $text .=
'(' . implode(
', ', $frame[
'args'] ) .
")\n";
464 $text .=
"{$pad}#{$level} {main}";
481 return static::redactTrace( $e->getTrace() );
495 return array_map(
static function ( $frame ) {
496 if ( isset( $frame[
'args'] ) ) {
497 $frame[
'args'] = array_map(
static function ( $arg ) {
498 return is_object( $arg ) ? get_class( $arg ) : gettype( $arg );
533 $id = WebRequest::getRequestId();
534 $type = get_class( $e );
535 $message = $e->getMessage();
539 $message =
"A database query error has occurred. Did you forget to run"
540 .
" your application's database schema updater after upgrading"
541 .
" or after adding a new extension?\n\nPlease see"
542 .
" https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Upgrading and"
543 .
" https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:How_to_debug"
544 .
" for more information.\n\n"
548 return "[$id] $url $type: $message";
561 if ( $e instanceof INormalizedException ) {
562 $message = $e->getNormalizedMessage();
564 $message = $e->getMessage();
566 if ( !$e instanceof ErrorException ) {
571 $message = get_class( $e ) .
": $message";
574 return "[{reqId}] {exception_url} $message";
582 $reqId = WebRequest::getRequestId();
583 $type = get_class( $e );
584 return '[' . $reqId .
'] '
585 . gmdate(
'Y-m-d H:i:s' ) .
': '
586 .
'Fatal exception of type "' . $type .
'"';
601 public static function getLogContext( Throwable $e, $catcher = self::CAUGHT_BY_OTHER ) {
611 'reqId' => WebRequest::getRequestId(),
612 'caught_by' => $catcher
614 if ( $e instanceof INormalizedException ) {
615 $context += $e->getMessageContext();
634 $catcher = self::CAUGHT_BY_OTHER
637 'id' => WebRequest::getRequestId(),
638 'type' => get_class( $e ),
639 'file' => $e->getFile(),
640 'line' => $e->getLine(),
641 'message' => $e->getMessage(),
642 'code' => $e->getCode(),
644 'caught_by' => $catcher
647 if ( $e instanceof ErrorException &&
648 ( error_reporting() & $e->getSeverity() ) === 0
651 $data[
'suppressed'] =
true;
654 if ( self::$logExceptionBacktrace ) {
658 $previous = $e->getPrevious();
659 if ( $previous !==
null ) {
724 $catcher = self::CAUGHT_BY_OTHER
727 self::getStructuredExceptionData( $e, $catcher ),
746 $catcher = self::CAUGHT_BY_OTHER,
749 if ( !( $e instanceof
MWException ) || $e->isLoggable() ) {
750 $logger = LoggerFactory::getInstance(
'exception' );
753 $context[
'extraData'] = $extraData;
756 self::getLogNormalMessage( $e ),
761 if ( $json !==
false ) {
762 $logger = LoggerFactory::getInstance(
'exception-json' );
763 $logger->error( $json, [
'private' =>
true ] );
766 (
new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )->onLogException( $e,
false );
778 private static function logError(
787 $suppressed = ( error_reporting() & $e->getSeverity() ) === 0;
788 if ( !$suppressed ) {
789 $logger = LoggerFactory::getInstance( $channel );
792 self::getLogNormalMessage( $e ),
793 self::getLogContext( $e, $catcher )
799 if ( $json !==
false ) {
800 $logger = LoggerFactory::getInstance(
"{$channel}-json" );
807 $unfilteredLevel = $suppressed ? LogLevel::DEBUG : $level;
808 $logger->log( $unfilteredLevel, $json, [
'private' =>
true ] );
811 (
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)
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.