use DateTimeZone;
use MWDebug;
+use MWExceptionHandler;
use Psr\Log\AbstractLogger;
use Psr\Log\LogLevel;
use UDPTransport;
* @return string
*/
public static function format( $channel, $message, $context ) {
- global $wgDebugLogGroups;
+ global $wgDebugLogGroups, $wgLogExceptionBacktrace;
if ( $channel === 'wfDebug' ) {
$text = self::formatAsWfDebug( $channel, $message, $context );
$text = self::formatAsWfDebugLog( $channel, $message, $context );
}
+ // Append stacktrace of exception if available
+ if ( $wgLogExceptionBacktrace &&
+ isset( $context['exception'] ) &&
+ $context['exception'] instanceof Exception
+ ) {
+ $text .= MWExceptionHandler::getRedactedTraceAsString(
+ $context['exception']->getTraceAsString()
+ ) . "\n";
+ }
+
return self::interpolate( $text, $context );
}
if ( strpos( $message, '{' ) !== false ) {
$replace = array();
foreach ( $context as $key => $val ) {
- $replace['{' . $key . '}'] = $val;
+ $replace['{' . $key . '}'] = self::flatten( $val );
}
$message = strtr( $message, $replace );
}
}
+ /**
+ * Convert a logging context element to a string suitable for
+ * interpolation.
+ *
+ * @param mixed $item
+ * @return string
+ */
+ protected static function flatten( $item ) {
+ if ( null === $item ) {
+ return '[Null]';
+ }
+
+ if ( is_bool( $item ) ) {
+ return $item ? 'true' : 'false';
+ }
+
+ if ( is_float( $item ) ) {
+ if ( is_infinite( $item ) ) {
+ return ( $item > 0 ? '' : '-' ) . 'INF';
+ }
+ if ( is_nan( $item ) ) {
+ return 'NaN';
+ }
+ return $data;
+ }
+
+ if ( is_scalar( $item ) ) {
+ return (string) $item;
+ }
+
+ if ( is_array( $item ) ) {
+ return '[Array(' . count( $item ) . ')]';
+ }
+
+ if ( $item instanceof \DateTime ) {
+ return $item->format( 'c' );
+ }
+
+ if ( $item instanceof \Exception ) {
+ return '[Exception ' . get_class( $item ) . '( ' .
+ $item->getFile() . ':' . $item->getLine() . ') ' .
+ $item->getMessage() . ']';
+ }
+
+ if ( is_object( $item ) ) {
+ if ( method_exists( $item, '__toString' ) ) {
+ return (string) $item;
+ }
+
+ return '[Object ' . get_class( $item ) . ']';
+ }
+
+ if ( is_resource( $item ) ) {
+ return '[Resource ' . get_resource_type( $item ) . ']';
+ }
+
+ return '[Unknown ' . gettype( $item ) . ']';
+ }
+
+
/**
* Select the appropriate log output destination for the given log event.
*
* @file
*/
+use MediaWiki\Logger\LoggerFactory;
+
/**
* Handler class for MWExceptions
* @ingroup Exception
$message = "Exception encountered, of type \"" . get_class( $e ) . "\"";
if ( $wgShowExceptionDetails ) {
- $message .= "\n" . MWExceptionHandler::getLogMessage( $e ) . "\nBacktrace:\n" .
+ $message .= "\n" . self::getLogMessage( $e ) . "\nBacktrace:\n" .
self::getRedactedTraceAsString( $e ) . "\n";
}
public static function rollbackMasterChangesAndLog( Exception $e ) {
$factory = wfGetLBFactory();
if ( $factory->hasMasterChanges() ) {
- wfDebugLog( 'Bug56269',
+ $logger = LoggerFactory::getInstance( 'Bug56269' );
+ $logger->warning(
'Exception thrown with an uncommited database transaction: ' .
- MWExceptionHandler::getLogMessage( $e ) . "\n" .
- $e->getTraceAsString()
+ self::getLogMessage( $e ),
+ self::getLogContext( $e )
);
$factory->rollbackMasterChanges();
}
* @return string
*/
public static function getRedactedTraceAsString( Exception $e ) {
+ return self::prettyPrintRedactedTrace(
+ self::getRedactedTrace( $e )
+ );
+ }
+
+ /**
+ * Generate a string representation of a structured stack trace generated
+ * by getRedactedTrace().
+ *
+ * @param array $trace
+ * @return string
+ * @since 1.26
+ */
+ public static function prettyPrintRedactedTrace( array $trace ) {
$text = '';
- foreach ( self::getRedactedTrace( $e ) as $level => $frame ) {
+ foreach ( $trace as $level => $frame ) {
if ( isset( $frame['file'] ) && isset( $frame['line'] ) ) {
$text .= "#{$level} {$frame['file']}({$frame['line']}): ";
} else {
return "[$id] $url $type from line $line of $file: $message";
}
+ /**
+ * 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 $e
+ * @return array
+ */
+ public static function getLogContext( Exception $e ) {
+ return array(
+ 'exception' => $e,
+ );
+ }
+
+ /**
+ * 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 $e
+ * @return array
+ * @since 1.26
+ */
+ public static function getStructuredExceptionData( Exception $e ) {
+ global $wgLogExceptionBacktrace;
+ $data = array(
+ 'id' => self::getLogId( $e ),
+ 'type' => get_class( $e ),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ 'message' => $e->getMessage(),
+ 'code' => $e->getCode(),
+ 'url' => self::getURL() ?: null,
+ );
+
+ 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 );
+ }
+
+ return $data;
+ }
+
/**
* Serialize an Exception object to JSON.
*
* @return string|false JSON string if successful; false upon failure
*/
public static function jsonSerializeException( Exception $e, $pretty = false, $escaping = 0 ) {
- global $wgLogExceptionBacktrace;
-
- $exceptionData = array(
- 'id' => self::getLogId( $e ),
- 'type' => get_class( $e ),
- 'file' => $e->getFile(),
- 'line' => $e->getLine(),
- 'message' => $e->getMessage(),
- );
-
- if ( $e instanceof ErrorException && ( error_reporting() & $e->getSeverity() ) === 0 ) {
- // Flag surpressed errors
- $exceptionData['suppressed'] = true;
- }
-
- // Because MediaWiki is first and foremost a web application, we set a
- // 'url' key unconditionally, but set it to null if the exception does
- // not occur in the context of a web request, as a way of making that
- // fact visible and explicit.
- $exceptionData['url'] = self::getURL() ?: null;
-
- if ( $wgLogExceptionBacktrace ) {
- // Argument values may not be serializable, so redact them.
- $exceptionData['backtrace'] = self::getRedactedTrace( $e );
- }
-
- return FormatJson::encode( $exceptionData, $pretty, $escaping );
+ $data = self::getStructuredExceptionData( $e );
+ return FormatJson::encode( $data, $pretty, $escaping );
}
/**
global $wgLogExceptionBacktrace;
if ( !( $e instanceof MWException ) || $e->isLoggable() ) {
- $log = self::getLogMessage( $e );
- if ( $wgLogExceptionBacktrace ) {
- wfDebugLog( 'exception', $log . "\n" . $e->getTraceAsString() );
- } else {
- wfDebugLog( 'exception', $log );
- }
+ $logger = LoggerFactory::getInstance( 'exception' );
+ $logger->error(
+ self::getLogMessage( $e ),
+ self::getLogContext( $e )
+ );
$json = self::jsonSerializeException( $e, false, FormatJson::ALL_OK );
if ( $json !== false ) {
- wfDebugLog( 'exception-json', $json, 'private' );
+ $logger = LoggerFactory::getInstance( 'exception-json' );
+ $logger->error( $json, array( 'private' => true ) );
}
Hooks::run( 'LogException', array( $e, false ) );
// Filter out unwanted errors manually (e.g. when MediaWiki\suppressWarnings is active).
$suppressed = ( error_reporting() & $e->getSeverity() ) === 0;
if ( !$suppressed ) {
- $log = self::getLogMessage( $e );
- if ( $wgLogExceptionBacktrace ) {
- wfDebugLog( $channel, $log . "\n" . $e->getTraceAsString() );
- } else {
- wfDebugLog( $channel, $log );
- }
+ $logger = LoggerFactory::getInstance( $channel );
+ $logger->error(
+ self::getLogMessage( $e ),
+ self::getLogContext( $e )
+ );
}
// Include all errors in the json log (surpressed errors will be flagged)
$json = self::jsonSerializeException( $e, false, FormatJson::ALL_OK );
if ( $json !== false ) {
- wfDebugLog( "$channel-json", $json, 'private' );
+ $logger = LoggerFactory::getInstance( "{$channel}-json" );
+ $logger->error( $json, array( 'private' => true ) );
}
Hooks::run( 'LogException', array( $e, $suppressed ) );