123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748 |
- <?php
- /**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
- use MediaWiki\Logger\LoggerFactory;
- use MediaWiki\MediaWikiServices;
- use Psr\Log\LogLevel;
- use Wikimedia\Rdbms\DBError;
- /**
- * Handler class for MWExceptions
- * @ingroup Exception
- */
- class MWExceptionHandler {
- const CAUGHT_BY_HANDLER = 'mwe_handler'; // error reported by this exception handler
- const CAUGHT_BY_OTHER = 'other'; // error reported by direct logException() call
- /**
- * @var string $reservedMemory
- */
- protected static $reservedMemory;
- /**
- * Error types that, if unhandled, are fatal to the request.
- *
- * On PHP 7, these error types may be thrown as Error objects, which
- * implement Throwable (but not Exception).
- *
- * On HHVM, these invoke the set_error_handler callback, similar to how
- * (non-fatal) warnings and notices are reported, except that after this
- * handler runs for fatal error tpyes, script execution stops!
- *
- * The user will be shown an HTTP 500 Internal Server Error.
- * As such, these should be sent to MediaWiki's "fatal" or "exception"
- * channel. Normally, the error handler logs them to the "error" channel.
- *
- * @var array $fatalErrorTypes
- */
- protected static $fatalErrorTypes = [
- E_ERROR,
- E_PARSE,
- E_CORE_ERROR,
- E_COMPILE_ERROR,
- E_USER_ERROR,
- // E.g. "Catchable fatal error: Argument X must be Y, null given"
- E_RECOVERABLE_ERROR,
- // HHVM's FATAL_ERROR constant
- 16777217,
- ];
- /**
- * @var bool $handledFatalCallback
- */
- protected static $handledFatalCallback = false;
- /**
- * Install handlers with PHP.
- */
- public static function installHandler() {
- // This catches:
- // * Exception objects that were explicitly thrown but not
- // caught anywhere in the application. This is rare given those
- // would normally be caught at a high-level like MediaWiki::run (index.php),
- // api.php, or ResourceLoader::respond (load.php). These high-level
- // catch clauses would then call MWExceptionHandler::logException
- // or MWExceptionHandler::handleException.
- // If they are not caught, then they are handled here.
- // * Error objects (on PHP 7+), for issues that would historically
- // cause fatal errors but may now be caught as Throwable (not Exception).
- // Same as previous case, but more common to bubble to here instead of
- // caught locally because they tend to not be safe to recover from.
- // (e.g. argument TypeErorr, devision by zero, etc.)
- set_exception_handler( 'MWExceptionHandler::handleUncaughtException' );
- // This catches:
- // * Non-fatal errors (e.g. PHP Notice, PHP Warning, PHP Error) that do not
- // interrupt execution in any way. We log these in the background and then
- // continue execution.
- // * Fatal errors (on HHVM in PHP5 mode) where PHP 7 would throw Throwable.
- set_error_handler( 'MWExceptionHandler::handleError' );
- // This catches:
- // * Fatal error for which no Throwable is thrown (PHP 7), and no Error emitted (HHVM).
- // This includes Out-Of-Memory and Timeout fatals.
- //
- // Reserve 16k of memory so we can report OOM fatals
- self::$reservedMemory = str_repeat( ' ', 16384 );
- register_shutdown_function( 'MWExceptionHandler::handleFatalError' );
- }
- /**
- * Report an exception to the user
- * @param Exception|Throwable $e
- */
- protected static function report( $e ) {
- try {
- // Try and show the exception prettily, with the normal skin infrastructure
- if ( $e instanceof MWException ) {
- // Delegate to MWException until all subclasses are handled by
- // MWExceptionRenderer and MWException::report() has been
- // removed.
- $e->report();
- } else {
- MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_PRETTY );
- }
- } catch ( Exception $e2 ) {
- // Exception occurred from within exception handler
- // Show a simpler message for the original exception,
- // don't try to invoke report()
- MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_RAW, $e2 );
- }
- }
- /**
- * Roll back any open database transactions and log the stack trace of the exception
- *
- * This method is used to attempt to recover from exceptions
- *
- * @since 1.23
- * @param Exception|Throwable $e
- */
- public static function rollbackMasterChangesAndLog( $e ) {
- $services = MediaWikiServices::getInstance();
- if ( !$services->isServiceDisabled( 'DBLoadBalancerFactory' ) ) {
- // Rollback DBs to avoid transaction notices. This might fail
- // to rollback some databases due to connection issues or exceptions.
- // However, any sane DB driver will rollback implicitly anyway.
- try {
- $services->getDBLoadBalancerFactory()->rollbackMasterChanges( __METHOD__ );
- } catch ( DBError $e2 ) {
- // If the DB is unreacheable, rollback() will throw an error
- // and the error report() method might need messages from the DB,
- // which would result in an exception loop. PHP may escalate such
- // errors to "Exception thrown without a stack frame" fatals, but
- // it's better to be explicit here.
- self::logException( $e2, self::CAUGHT_BY_HANDLER );
- }
- }
- self::logException( $e, self::CAUGHT_BY_HANDLER );
- }
- /**
- * Callback to use with PHP's set_exception_handler.
- *
- * @since 1.31
- * @param Exception|Throwable $e
- */
- public static function handleUncaughtException( $e ) {
- self::handleException( $e );
- // Make sure we don't claim success on exit for CLI scripts (T177414)
- if ( wfIsCLI() ) {
- register_shutdown_function(
- function () {
- exit( 255 );
- }
- );
- }
- }
- /**
- * Exception handler which simulates the appropriate catch() handling:
- *
- * try {
- * ...
- * } catch ( Exception $e ) {
- * $e->report();
- * } catch ( Exception $e ) {
- * echo $e->__toString();
- * }
- *
- * @since 1.25
- * @param Exception|Throwable $e
- */
- public static function handleException( $e ) {
- self::rollbackMasterChangesAndLog( $e );
- self::report( $e );
- }
- /**
- * Handler for set_error_handler() callback notifications.
- *
- * Receive a callback from the interpreter for a raised error, create an
- * ErrorException, and log the exception to the 'error' logging
- * channel(s). If the raised error is a fatal error type (only under HHVM)
- * delegate to handleFatalError() instead.
- *
- * @since 1.25
- *
- * @param int $level Error level raised
- * @param string $message
- * @param string|null $file
- * @param int|null $line
- * @return bool
- *
- * @see logError()
- */
- public static function handleError(
- $level, $message, $file = null, $line = null
- ) {
- global $wgPropagateErrors;
- if ( in_array( $level, self::$fatalErrorTypes ) ) {
- return self::handleFatalError( ...func_get_args() );
- }
- // Map PHP error constant to a PSR-3 severity level.
- // Avoid use of "DEBUG" or "INFO" levels, unless the
- // error should evade error monitoring and alerts.
- //
- // To decide the log level, ask yourself: "Has the
- // program's behaviour diverged from what the written
- // code expected?"
- //
- // For example, use of a deprecated method or violating a strict standard
- // has no impact on functional behaviour (Warning). On the other hand,
- // accessing an undefined variable makes behaviour diverge from what the
- // author intended/expected. PHP recovers from an undefined variables by
- // yielding null and continuing execution, but it remains a change in
- // behaviour given the null was not part of the code and is likely not
- // accounted for.
- switch ( $level ) {
- case E_WARNING:
- case E_CORE_WARNING:
- case E_COMPILE_WARNING:
- $levelName = 'Warning';
- $severity = LogLevel::ERROR;
- break;
- case E_NOTICE:
- $levelName = 'Notice';
- $severity = LogLevel::ERROR;
- break;
- case E_USER_NOTICE:
- // Used by wfWarn(), MWDebug::warning()
- $levelName = 'Notice';
- $severity = LogLevel::WARNING;
- break;
- case E_USER_WARNING:
- // Used by wfWarn(), MWDebug::warning()
- $levelName = 'Warning';
- $severity = LogLevel::WARNING;
- break;
- case E_STRICT:
- $levelName = 'Strict Standards';
- $severity = LogLevel::WARNING;
- break;
- case E_DEPRECATED:
- case E_USER_DEPRECATED:
- $levelName = 'Deprecated';
- $severity = LogLevel::WARNING;
- break;
- default:
- $levelName = 'Unknown error';
- $severity = LogLevel::ERROR;
- break;
- }
- $e = new ErrorException( "PHP $levelName: $message", 0, $level, $file, $line );
- self::logError( $e, 'error', $severity );
- // If $wgPropagateErrors is true return false so PHP shows/logs the error normally.
- // Ignore $wgPropagateErrors if track_errors is set
- // (which means someone is counting on regular PHP error handling behavior).
- return !( $wgPropagateErrors || ini_get( 'track_errors' ) );
- }
- /**
- * Dual purpose callback used as both a set_error_handler() callback and
- * a registered shutdown function. Receive a callback from the interpreter
- * for a raised error or system shutdown, check for a fatal error, and log
- * to the 'fatal' logging channel.
- *
- * Special handling is included for missing class errors as they may
- * indicate that the user needs to install 3rd-party libraries via
- * Composer or other means.
- *
- * @since 1.25
- *
- * @param int|null $level Error level raised
- * @param string|null $message Error message
- * @param string|null $file File that error was raised in
- * @param int|null $line Line number error was raised at
- * @param array|null $context Active symbol table point of error
- * @param array|null $trace Backtrace at point of error (undocumented HHVM
- * feature)
- * @return bool Always returns false
- */
- public static function handleFatalError(
- $level = null, $message = null, $file = null, $line = null,
- $context = null, $trace = null
- ) {
- // Free reserved memory so that we have space to process OOM
- // errors
- self::$reservedMemory = null;
- if ( $level === null ) {
- // Called as a shutdown handler, get data from error_get_last()
- if ( static::$handledFatalCallback ) {
- // Already called once (probably as an error handler callback
- // under HHVM) so don't log again.
- return false;
- }
- $lastError = error_get_last();
- if ( $lastError !== null ) {
- $level = $lastError['type'];
- $message = $lastError['message'];
- $file = $lastError['file'];
- $line = $lastError['line'];
- } else {
- $level = 0;
- $message = '';
- }
- }
- if ( !in_array( $level, self::$fatalErrorTypes ) ) {
- // Only interested in fatal errors, others should have been
- // handled by MWExceptionHandler::handleError
- return false;
- }
- $url = WebRequest::getGlobalRequestURL();
- $msgParts = [
- '[{exception_id}] {exception_url} PHP Fatal Error',
- ( $line || $file ) ? ' from' : '',
- $line ? " line $line" : '',
- ( $line && $file ) ? ' of' : '',
- $file ? " $file" : '',
- ": $message",
- ];
- $msg = implode( '', $msgParts );
- // Look at message to see if this is a class not found failure
- // HHVM: Class undefined: foo
- // PHP7: Class 'foo' not found
- if ( preg_match( "/Class (undefined: \w+|'\w+' not found)/", $message ) ) {
- // phpcs:disable Generic.Files.LineLength
- $msg = <<<TXT
- {$msg}
- 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.
- 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.
- TXT;
- // phpcs:enable
- }
- // We can't just create an exception and log it as it is likely that
- // the interpreter has unwound the stack already. If that is true the
- // stacktrace we would get would be functionally empty. If however we
- // have been called as an error handler callback *and* HHVM is in use
- // we will have been provided with a useful stacktrace that we can
- // log.
- $trace = $trace ?: debug_backtrace();
- $logger = LoggerFactory::getInstance( 'fatal' );
- $logger->error( $msg, [
- 'fatal_exception' => [
- 'class' => ErrorException::class,
- 'message' => "PHP Fatal Error: {$message}",
- 'code' => $level,
- 'file' => $file,
- 'line' => $line,
- 'trace' => self::prettyPrintTrace( self::redactTrace( $trace ) ),
- ],
- 'exception_id' => WebRequest::getRequestId(),
- 'exception_url' => $url,
- 'caught_by' => self::CAUGHT_BY_HANDLER
- ] );
- // Remember call so we don't double process via HHVM's fatal
- // notifications and the shutdown hook behavior
- static::$handledFatalCallback = true;
- return false;
- }
- /**
- * Generate a string representation of an exception's stack trace
- *
- * Like Exception::getTraceAsString, but replaces argument values with
- * argument type or class name.
- *
- * @param Exception|Throwable $e
- * @return string
- * @see prettyPrintTrace()
- */
- public static function getRedactedTraceAsString( $e ) {
- return self::prettyPrintTrace( self::getRedactedTrace( $e ) );
- }
- /**
- * Generate a string representation of a stacktrace.
- *
- * @param array $trace
- * @param string $pad Constant padding to add to each line of trace
- * @return string
- * @since 1.26
- */
- public static function prettyPrintTrace( array $trace, $pad = '' ) {
- $text = '';
- $level = 0;
- foreach ( $trace as $level => $frame ) {
- if ( isset( $frame['file'] ) && isset( $frame['line'] ) ) {
- $text .= "{$pad}#{$level} {$frame['file']}({$frame['line']}): ";
- } else {
- // 'file' and 'line' are unset for calls via call_user_func
- // (T57634) This matches behaviour of
- // Exception::getTraceAsString to instead display "[internal
- // function]".
- $text .= "{$pad}#{$level} [internal function]: ";
- }
- if ( isset( $frame['class'] ) && isset( $frame['type'] ) && isset( $frame['function'] ) ) {
- $text .= $frame['class'] . $frame['type'] . $frame['function'];
- } elseif ( isset( $frame['function'] ) ) {
- $text .= $frame['function'];
- } else {
- $text .= 'NO_FUNCTION_GIVEN';
- }
- if ( isset( $frame['args'] ) ) {
- $text .= '(' . implode( ', ', $frame['args'] ) . ")\n";
- } else {
- $text .= "()\n";
- }
- }
- $level = $level + 1;
- $text .= "{$pad}#{$level} {main}";
- return $text;
- }
- /**
- * Return a copy of an exception's backtrace as an array.
- *
- * Like Exception::getTrace, but replaces each element in each frame's
- * argument array with the name of its class (if the element is an object)
- * or its type (if the element is a PHP primitive).
- *
- * @since 1.22
- * @param Exception|Throwable $e
- * @return array
- */
- public static function getRedactedTrace( $e ) {
- return static::redactTrace( $e->getTrace() );
- }
- /**
- * Redact a stacktrace generated by Exception::getTrace(),
- * debug_backtrace() or similar means. Replaces each element in each
- * frame's argument array with the name of its class (if the element is an
- * object) or its type (if the element is a PHP primitive).
- *
- * @since 1.26
- * @param array $trace Stacktrace
- * @return array Stacktrace with arugment values converted to data types
- */
- public static function redactTrace( array $trace ) {
- return array_map( function ( $frame ) {
- if ( isset( $frame['args'] ) ) {
- $frame['args'] = array_map( function ( $arg ) {
- return is_object( $arg ) ? get_class( $arg ) : gettype( $arg );
- }, $frame['args'] );
- }
- return $frame;
- }, $trace );
- }
- /**
- * If the exception occurred in the course of responding to a request,
- * returns the requested URL. Otherwise, returns false.
- *
- * @since 1.23
- * @return string|false
- */
- public static function getURL() {
- global $wgRequest;
- if ( !isset( $wgRequest ) || $wgRequest instanceof FauxRequest ) {
- return false;
- }
- return $wgRequest->getRequestURL();
- }
- /**
- * Get a message formatting the exception message and its origin.
- *
- * @since 1.22
- * @param Exception|Throwable $e
- * @return string
- */
- public static function getLogMessage( $e ) {
- $id = WebRequest::getRequestId();
- $type = get_class( $e );
- $file = $e->getFile();
- $line = $e->getLine();
- $message = $e->getMessage();
- $url = self::getURL() ?: '[no req]';
- return "[$id] $url $type from line $line of $file: $message";
- }
- /**
- * Get a normalised message for formatting with PSR-3 log event context.
- *
- * Must be used together with `getLogContext()` to be useful.
- *
- * @since 1.30
- * @param Exception|Throwable $e
- * @return string
- */
- public static function getLogNormalMessage( $e ) {
- $type = get_class( $e );
- $file = $e->getFile();
- $line = $e->getLine();
- $message = $e->getMessage();
- return "[{exception_id}] {exception_url} $type from line $line of $file: $message";
- }
- /**
- * @param Exception|Throwable $e
- * @return string
- */
- public static function getPublicLogMessage( $e ) {
- $reqId = WebRequest::getRequestId();
- $type = get_class( $e );
- return '[' . $reqId . '] '
- . gmdate( 'Y-m-d H:i:s' ) . ': '
- . 'Fatal exception of type "' . $type . '"';
- }
- /**
- * Get a PSR-3 log event context from an Exception.
- *
- * Creates a structured array containing information about the provided
- * exception that can be used to augment a log message sent to a PSR-3
- * logger.
- *
- * @param Exception|Throwable $e
- * @param string $catcher CAUGHT_BY_* class constant indicating what caught the error
- * @return array
- */
- public static function getLogContext( $e, $catcher = self::CAUGHT_BY_OTHER ) {
- return [
- 'exception' => $e,
- 'exception_id' => WebRequest::getRequestId(),
- 'exception_url' => self::getURL() ?: '[no req]',
- 'caught_by' => $catcher
- ];
- }
- /**
- * Get a structured representation of an Exception.
- *
- * Returns an array of structured data (class, message, code, file,
- * backtrace) derived from the given exception. The backtrace information
- * will be redacted as per getRedactedTraceAsArray().
- *
- * @param Exception|Throwable $e
- * @param string $catcher CAUGHT_BY_* class constant indicating what caught the error
- * @return array
- * @since 1.26
- */
- public static function getStructuredExceptionData( $e, $catcher = self::CAUGHT_BY_OTHER ) {
- global $wgLogExceptionBacktrace;
- $data = [
- 'id' => WebRequest::getRequestId(),
- 'type' => get_class( $e ),
- 'file' => $e->getFile(),
- 'line' => $e->getLine(),
- 'message' => $e->getMessage(),
- 'code' => $e->getCode(),
- 'url' => self::getURL() ?: null,
- 'caught_by' => $catcher
- ];
- if ( $e instanceof ErrorException &&
- ( error_reporting() & $e->getSeverity() ) === 0
- ) {
- // Flag surpressed errors
- $data['suppressed'] = true;
- }
- if ( $wgLogExceptionBacktrace ) {
- $data['backtrace'] = self::getRedactedTrace( $e );
- }
- $previous = $e->getPrevious();
- if ( $previous !== null ) {
- $data['previous'] = self::getStructuredExceptionData( $previous, $catcher );
- }
- return $data;
- }
- /**
- * Serialize an Exception object to JSON.
- *
- * The JSON object will have keys 'id', 'file', 'line', 'message', and
- * 'url'. These keys map to string values, with the exception of 'line',
- * which is a number, and 'url', which may be either a string URL or or
- * null if the exception did not occur in the context of serving a web
- * request.
- *
- * If $wgLogExceptionBacktrace is true, it will also have a 'backtrace'
- * key, mapped to the array return value of Exception::getTrace, but with
- * each element in each frame's "args" array (if set) replaced with the
- * argument's class name (if the argument is an object) or type name (if
- * the argument is a PHP primitive).
- *
- * @par Sample JSON record ($wgLogExceptionBacktrace = false):
- * @code
- * {
- * "id": "c41fb419",
- * "type": "MWException",
- * "file": "/var/www/mediawiki/includes/cache/MessageCache.php",
- * "line": 704,
- * "message": "Non-string key given",
- * "url": "/wiki/Main_Page"
- * }
- * @endcode
- *
- * @par Sample JSON record ($wgLogExceptionBacktrace = true):
- * @code
- * {
- * "id": "dc457938",
- * "type": "MWException",
- * "file": "/vagrant/mediawiki/includes/cache/MessageCache.php",
- * "line": 704,
- * "message": "Non-string key given",
- * "url": "/wiki/Main_Page",
- * "backtrace": [{
- * "file": "/vagrant/mediawiki/extensions/VisualEditor/VisualEditor.hooks.php",
- * "line": 80,
- * "function": "get",
- * "class": "MessageCache",
- * "type": "->",
- * "args": ["array"]
- * }]
- * }
- * @endcode
- *
- * @since 1.23
- * @param Exception|Throwable $e
- * @param bool $pretty Add non-significant whitespace to improve readability (default: false).
- * @param int $escaping Bitfield consisting of FormatJson::.*_OK class constants.
- * @param string $catcher CAUGHT_BY_* class constant indicating what caught the error
- * @return string|false JSON string if successful; false upon failure
- */
- public static function jsonSerializeException(
- $e, $pretty = false, $escaping = 0, $catcher = self::CAUGHT_BY_OTHER
- ) {
- return FormatJson::encode(
- self::getStructuredExceptionData( $e, $catcher ),
- $pretty,
- $escaping
- );
- }
- /**
- * Log an exception to the exception log (if enabled).
- *
- * This method must not assume the exception is an MWException,
- * it is also used to handle PHP exceptions or exceptions from other libraries.
- *
- * @param Exception|Throwable $e
- * @param string $catcher CAUGHT_BY_* class constant indicating what caught the error
- * @param array $extraData (since 1.34) Additional data to log
- * @since 1.22
- */
- public static function logException( $e, $catcher = self::CAUGHT_BY_OTHER, $extraData = [] ) {
- if ( !( $e instanceof MWException ) || $e->isLoggable() ) {
- $logger = LoggerFactory::getInstance( 'exception' );
- $context = self::getLogContext( $e, $catcher );
- if ( $extraData ) {
- $context['extraData'] = $extraData;
- }
- $logger->error(
- self::getLogNormalMessage( $e ),
- $context
- );
- $json = self::jsonSerializeException( $e, false, FormatJson::ALL_OK, $catcher );
- if ( $json !== false ) {
- $logger = LoggerFactory::getInstance( 'exception-json' );
- $logger->error( $json, [ 'private' => true ] );
- }
- Hooks::run( 'LogException', [ $e, false ] );
- }
- }
- /**
- * Log an exception that wasn't thrown but made to wrap an error.
- *
- * @since 1.25
- * @param ErrorException $e
- * @param string $channel
- * @param string $level
- */
- protected static function logError(
- ErrorException $e, $channel, $level = LogLevel::ERROR
- ) {
- $catcher = self::CAUGHT_BY_HANDLER;
- // The set_error_handler callback is independent from error_reporting.
- // Filter out unwanted errors manually (e.g. when
- // Wikimedia\suppressWarnings is active).
- $suppressed = ( error_reporting() & $e->getSeverity() ) === 0;
- if ( !$suppressed ) {
- $logger = LoggerFactory::getInstance( $channel );
- $logger->log(
- $level,
- self::getLogNormalMessage( $e ),
- self::getLogContext( $e, $catcher )
- );
- }
- // Include all errors in the json log (surpressed errors will be flagged)
- $json = self::jsonSerializeException( $e, false, FormatJson::ALL_OK, $catcher );
- if ( $json !== false ) {
- $logger = LoggerFactory::getInstance( "{$channel}-json" );
- $logger->log( $level, $json, [ 'private' => true ] );
- }
- Hooks::run( 'LogException', [ $e, $suppressed ] );
- }
- }
|