aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--includes/debug/logger/monolog/LogstashFormatter.php110
-rw-r--r--tests/phpunit/unit/includes/debug/logger/monolog/LogstashFormatterTest.php8
2 files changed, 103 insertions, 15 deletions
diff --git a/includes/debug/logger/monolog/LogstashFormatter.php b/includes/debug/logger/monolog/LogstashFormatter.php
index 3740d4121bf6..178562670b2f 100644
--- a/includes/debug/logger/monolog/LogstashFormatter.php
+++ b/includes/debug/logger/monolog/LogstashFormatter.php
@@ -11,6 +11,10 @@ namespace MediaWiki\Logger\Monolog;
* @since 1.29
*/
class LogstashFormatter extends \Monolog\Formatter\LogstashFormatter {
+
+ public const V0 = 0;
+ public const V1 = 1;
+
/** @var array Keys which should not be used in log context */
protected $reservedKeys = [
// from LogstashFormatter
@@ -24,9 +28,12 @@ class LogstashFormatter extends \Monolog\Formatter\LogstashFormatter {
];
/**
- * Constructor exists to keep the default values for $extraKey and $contextKey as they were
- * in monolog/monolog 1.25.3 (null), rather than the populated default string that they are
- * in >=2.0. See T247675 for removing this override.
+ * @var int Logstash format version to use
+ */
+ protected $version;
+
+ /**
+ * See T247675 for removing this override.
*
* @param string $applicationName The application that sends the data, used as the "type"
* field of logstash
@@ -34,41 +41,120 @@ class LogstashFormatter extends \Monolog\Formatter\LogstashFormatter {
* logstash, defaults to the hostname of the machine
* @param string $extraKey The key for extra keys inside logstash "fields", defaults to ''
* @param string $contextKey The key for context keys inside logstash "fields", defaults
+ * @param int $version The logstash format version to use, defaults to V0
* to ''
*/
public function __construct( string $applicationName, ?string $systemName = null,
- string $extraKey = '', string $contextKey = ''
+ string $extraKey = '', string $contextKey = 'ctxt_', $version = self::V0
) {
+ $this->version = $version;
parent::__construct( $applicationName, $systemName, $extraKey, $contextKey );
}
+ public function format( array $record ): string {
+ $record = \Monolog\Formatter\NormalizerFormatter::format( $record );
+ if ( $this->version === self::V1 ) {
+ $message = $this->formatv1( $record );
+ } elseif ( $this->version === self::V0 ) {
+ $message = $this->formatV0( $record );
+ }
+
+ return $this->toJson( $message ) . "\n";
+ }
+
/**
* Prevent key conflicts
* @param array $record
- * @return string
+ * @return array
*/
- public function format( array $record ): string {
+ protected function formatV0( array $record ) {
if ( $this->contextKey !== '' ) {
- return $this->toJson( $this->formatV1( $record ) ) . "\n";
+ return $this->formatMonologV0( $record );
}
$context = !empty( $record['context'] ) ? $record['context'] : [];
$record['context'] = [];
+ $formatted = $this->formatMonologV0( $record );
- $formatted = $this->formatV1( $record );
+ $formatted['@fields'] = $this->fixKeyConflicts( $formatted['@fields'], $context );
- return $this->toJson( $this->fixKeyConflicts( $formatted, $context ) ) . "\n";
+ return $formatted;
}
/**
* Borrowed from monolog/monolog 1.25.3
- * https://github.com/Seldaek/monolog/blob/1.25.3/src/Monolog/Formatter/LogstashFormatter.php#L130-L165
- * https://github.com/Seldaek/monolog/blob/2.0.0/src/Monolog/Formatter/LogstashFormatter.php#L86-L88
+ * https://github.com/Seldaek/monolog/blob/1.x/src/Monolog/Formatter/LogstashFormatter.php#L87-L128
*
* @param array $record
* @return array
*/
+ protected function formatMonologV0( array $record ) {
+ if ( empty( $record['datetime'] ) ) {
+ $record['datetime'] = gmdate( 'c' );
+ }
+ $message = [
+ '@timestamp' => $record['datetime'],
+ '@source' => $this->systemName,
+ '@fields' => [],
+ ];
+ if ( isset( $record['message'] ) ) {
+ $message['@message'] = $record['message'];
+ }
+ if ( isset( $record['channel'] ) ) {
+ $message['@tags'] = [ $record['channel'] ];
+ $message['@fields']['channel'] = $record['channel'];
+ }
+ if ( isset( $record['level'] ) ) {
+ $message['@fields']['level'] = $record['level'];
+ }
+ if ( $this->applicationName ) {
+ $message['@type'] = $this->applicationName;
+ }
+ if ( isset( $record['extra']['server'] ) ) {
+ $message['@source_host'] = $record['extra']['server'];
+ }
+ if ( isset( $record['extra']['url'] ) ) {
+ $message['@source_path'] = $record['extra']['url'];
+ }
+ if ( !empty( $record['extra'] ) ) {
+ foreach ( $record['extra'] as $key => $val ) {
+ $message['@fields'][$this->extraKey . $key] = $val;
+ }
+ }
+ if ( !empty( $record['context'] ) ) {
+ foreach ( $record['context'] as $key => $val ) {
+ $message['@fields'][$this->contextKey . $key] = $val;
+ }
+ }
+
+ return $message;
+ }
+
+ /**
+ * Prevent key conflicts
+ * @param array $record
+ * @return array
+ */
protected function formatV1( array $record ) {
+ if ( $this->contextKey ) {
+ return $this->formatMonologV1( $record );
+ }
+
+ $context = !empty( $record['context'] ) ? $record['context'] : [];
+ $record['context'] = [];
+ $formatted = $this->formatMonologV1( $record );
+
+ return $this->fixKeyConflicts( $formatted, $context );
+ }
+
+ /**
+ * Borrowed mostly from monolog/monolog 1.25.3
+ * https://github.com/Seldaek/monolog/blob/1.25.3/src/Monolog/Formatter/LogstashFormatter.php#L130-165
+ *
+ * @param array $record
+ * @return array
+ */
+ protected function formatMonologV1( array $record ) {
if ( empty( $record['datetime'] ) ) {
$record['datetime'] = gmdate( 'c' );
}
@@ -87,6 +173,8 @@ class LogstashFormatter extends \Monolog\Formatter\LogstashFormatter {
if ( isset( $record['level_name'] ) ) {
$message['level'] = $record['level_name'];
}
+ // level -> monolog_level is new in 2.0
+ // https://github.com/Seldaek/monolog/blob/2.0.2/src/Monolog/Formatter/LogstashFormatter.php#L86-L88
if ( isset( $record['level'] ) ) {
$message['monolog_level'] = $record['level'];
}
diff --git a/tests/phpunit/unit/includes/debug/logger/monolog/LogstashFormatterTest.php b/tests/phpunit/unit/includes/debug/logger/monolog/LogstashFormatterTest.php
index 54a08508385e..fe015f2abd85 100644
--- a/tests/phpunit/unit/includes/debug/logger/monolog/LogstashFormatterTest.php
+++ b/tests/phpunit/unit/includes/debug/logger/monolog/LogstashFormatterTest.php
@@ -5,13 +5,13 @@ namespace MediaWiki\Logger\Monolog;
class LogstashFormatterTest extends \MediaWikiUnitTestCase {
/**
* @dataProvider provideV1
- * @covers MediaWiki\Logger\Monolog\LogstashFormatter::format
+ * @covers \MediaWiki\Logger\Monolog\LogstashFormatter::format
* @param array $record The input record.
* @param array $expected Associative array of expected keys and their values.
* @param array $notExpected List of keys that should not exist.
*/
public function testV1( array $record, array $expected, array $notExpected ) {
- $formatter = new LogstashFormatter( 'app', 'system' );
+ $formatter = new LogstashFormatter( 'app', 'system', '', '', LogstashFormatter::V1 );
$formatted = json_decode( $formatter->format( $record ), true );
foreach ( $expected as $key => $value ) {
$this->assertArrayHasKey( $key, $formatted );
@@ -44,10 +44,10 @@ class LogstashFormatterTest extends \MediaWikiUnitTestCase {
}
/**
- * @covers MediaWiki\Logger\Monolog\LogstashFormatter::format
+ * @covers \MediaWiki\Logger\Monolog\LogstashFormatter::format
*/
public function testV1WithPrefix() {
- $formatter = new LogstashFormatter( 'app', 'system', '', 'ctx_' );
+ $formatter = new LogstashFormatter( 'app', 'system', '', 'ctx_', LogstashFormatter::V1 );
$record = [ 'extra' => [ 'url' => 1 ], 'context' => [ 'url' => 2 ] ];
$formatted = json_decode( $formatter->format( $record ), true );
$this->assertArrayHasKey( 'url', $formatted );