namespace MediaWiki\Logger;
use DateTimeZone;
+use Error;
use Exception;
use WikiMap;
use MWDebug;
use MWExceptionHandler;
use Psr\Log\AbstractLogger;
use Psr\Log\LogLevel;
+use Throwable;
use UDPTransport;
/**
$e = $context['exception'];
$backtrace = false;
- if ( $e instanceof Exception ) {
+ if ( $e instanceof Throwable || $e instanceof Exception ) {
$backtrace = MWExceptionHandler::getRedactedTrace( $e );
} elseif ( is_array( $e ) && isset( $e['trace'] ) ) {
return $item->format( 'c' );
}
- if ( $item instanceof Exception ) {
- return '[Exception ' . get_class( $item ) . '( ' .
+ if ( $item instanceof Throwable || $item instanceof Exception ) {
+ $which = $item instanceof Error ? 'Error' : 'Exception';
+ return '[' . $which . ' ' . get_class( $item ) . '( ' .
$item->getFile() . ':' . $item->getLine() . ') ' .
$item->getMessage() . ']';
}
namespace MediaWiki\Logger\Monolog;
+use Error;
use Exception;
use Monolog\Formatter\LineFormatter as MonologLineFormatter;
use MWExceptionHandler;
+use Throwable;
/**
* Formats incoming records into a one-line string.
* excluded from '%context%' output if the '%exception%' placeholder is
* present.
*
- * Exceptions that are logged with this formatter will optional have their
+ * Throwables that are logged with this formatter will optional have their
* stack traces appended. If that is done, MWExceptionHandler::redactedTrace()
* will be used to redact the trace information.
*
$e = $record['context']['exception'];
unset( $record['context']['exception'] );
- if ( $e instanceof Exception ) {
+ if ( $e instanceof Throwable || $e instanceof Exception ) {
$prettyException = $this->normalizeException( $e );
} elseif ( is_array( $e ) ) {
$prettyException = $this->normalizeExceptionArray( $e );
}
/**
- * Convert an Exception to a string.
+ * Convert a Throwable to a string.
*
- * @param Exception $e
+ * @param Exception|Throwable $e
* @return string
*/
protected function normalizeException( $e ) {
}
/**
- * Convert an exception to an array of structured data.
+ * Convert a throwable to an array of structured data.
*
- * @param Exception $e
+ * @param Exception|Throwable $e
* @return array
*/
- protected function exceptionAsArray( Exception $e ) {
+ protected function exceptionAsArray( $e ) {
$out = [
'class' => get_class( $e ),
'message' => $e->getMessage(),
}
/**
- * Convert an array of Exception data to a string.
+ * Convert an array of Throwable data to a string.
*
* @param array $e
* @return string
];
$e = array_merge( $defaults, $e );
- $str = "\n[Exception {$e['class']}] (" .
+ $which = is_a( $e['class'], Error::class, true ) ? 'Error' : 'Exception';
+ $str = "\n[$which {$e['class']}] (" .
"{$e['file']}:{$e['line']}) {$e['message']}";
if ( $this->includeStacktraces && $e['trace'] ) {
$prev = $e['previous'];
while ( $prev ) {
$prev = array_merge( $defaults, $prev );
- $str .= "\nCaused by: [Exception {$prev['class']}] (" .
+ $which = is_a( $prev['class'], Error::class, true ) ? 'Error' : 'Exception';
+ $str .= "\nCaused by: [$which {$prev['class']}] (" .
"{$prev['file']}:{$prev['line']}) {$prev['message']}";
if ( $this->includeStacktraces && $prev['trace'] ) {
];
}
+ /**
+ * @covers MediaWiki\Logger\LegacyLogger::interpolate
+ */
+ public function testInterpolate_Error() {
+ // @todo Merge this into provideInterpolate once we drop HHVM support
+ if ( !class_exists( \Error::class ) ) {
+ $this->markTestSkipped( 'Error class does not exist' );
+ }
+
+ $err = new \Error( 'Test error' );
+ $message = '{exception}';
+ $context = [ 'exception' => $err ];
+ $expect = '[Error ' . get_class( $err ) . '( ' .
+ $err->getFile() . ':' . $err->getLine() . ') ' .
+ $err->getMessage() . ']';
+
+ $this->assertEquals(
+ $expect, LegacyLogger::interpolate( $message, $context ) );
+ }
+
/**
* @covers MediaWiki\Logger\LegacyLogger::shouldEmit
* @dataProvider provideShouldEmit
namespace MediaWiki\Logger\Monolog;
+use AssertionError;
use InvalidArgumentException;
use LengthException;
use LogicException;
$this->assertContains( "\nCaused by: [Exception LogicException]", $out );
$this->assertContains( "\n #0", $out );
}
+
+ /**
+ * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
+ */
+ public function testNormalizeExceptionErrorNoTrace() {
+ if ( !class_exists( AssertionError::class ) ) {
+ $this->markTestSkipped( 'AssertionError class does not exist' );
+ }
+
+ $fixture = new LineFormatter();
+ $fixture->includeStacktraces( false );
+ $fixture = TestingAccessWrapper::newFromObject( $fixture );
+ $boom = new InvalidArgumentException( 'boom', 0,
+ new LengthException( 'too long', 0,
+ new AssertionError( 'Spock wuz here' )
+ )
+ );
+ $out = $fixture->normalizeException( $boom );
+ $this->assertContains( "\n[Exception InvalidArgumentException]", $out );
+ $this->assertContains( "\nCaused by: [Exception LengthException]", $out );
+ $this->assertContains( "\nCaused by: [Error AssertionError]", $out );
+ $this->assertNotContains( "\n #0", $out );
+ }
+
+ /**
+ * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
+ */
+ public function testNormalizeExceptionErrorTrace() {
+ if ( !class_exists( AssertionError::class ) ) {
+ $this->markTestSkipped( 'AssertionError class does not exist' );
+ }
+
+ $fixture = new LineFormatter();
+ $fixture->includeStacktraces( true );
+ $fixture = TestingAccessWrapper::newFromObject( $fixture );
+ $boom = new InvalidArgumentException( 'boom', 0,
+ new LengthException( 'too long', 0,
+ new AssertionError( 'Spock wuz here' )
+ )
+ );
+ $out = $fixture->normalizeException( $boom );
+ $this->assertContains( "\n[Exception InvalidArgumentException]", $out );
+ $this->assertContains( "\nCaused by: [Exception LengthException]", $out );
+ $this->assertContains( "\nCaused by: [Error AssertionError]", $out );
+ $this->assertContains( "\n #0", $out );
+ }
}