MediaWiki  master
LogstashFormatter.php
Go to the documentation of this file.
1 <?php
2 
4 
13 class LogstashFormatter extends \Monolog\Formatter\LogstashFormatter {
14 
15  public const V0 = 0;
16  public const V1 = 1;
17 
19  protected $reservedKeys = [
20  // from LogstashFormatter
21  'message', 'channel', 'level', 'type',
22  // from WebProcessor
23  'url', 'ip', 'http_method', 'server', 'referrer',
24  // from WikiProcessor
25  'host', 'wiki', 'reqId', 'mwversion',
26  // from config magic
27  'normalized_message',
28  ];
29 
33  protected $version;
34 
47  public function __construct( string $applicationName, ?string $systemName = null,
48  string $extraKey = '', string $contextKey = 'ctxt_', $version = self::V0
49  ) {
50  $this->version = $version;
51  parent::__construct( $applicationName, $systemName, $extraKey, $contextKey );
52  }
53 
54  public function format( array $record ): string {
55  $record = \Monolog\Formatter\NormalizerFormatter::format( $record );
56  if ( $this->version === self::V1 ) {
57  $message = $this->formatv1( $record );
58  } elseif ( $this->version === self::V0 ) {
59  $message = $this->formatV0( $record );
60  }
61 
62  return $this->toJson( $message ) . "\n";
63  }
64 
70  protected function formatV0( array $record ) {
71  if ( $this->contextKey !== '' ) {
72  return $this->formatMonologV0( $record );
73  }
74 
75  $context = !empty( $record['context'] ) ? $record['context'] : [];
76  $record['context'] = [];
77  $formatted = $this->formatMonologV0( $record );
78 
79  $formatted['@fields'] = $this->fixKeyConflicts( $formatted['@fields'], $context );
80 
81  return $formatted;
82  }
83 
91  protected function formatMonologV0( array $record ) {
92  if ( empty( $record['datetime'] ) ) {
93  $record['datetime'] = gmdate( 'c' );
94  }
95  $message = [
96  '@timestamp' => $record['datetime'],
97  '@source' => $this->systemName,
98  '@fields' => [],
99  ];
100  if ( isset( $record['message'] ) ) {
101  $message['@message'] = $record['message'];
102  }
103  if ( isset( $record['channel'] ) ) {
104  $message['@tags'] = [ $record['channel'] ];
105  $message['@fields']['channel'] = $record['channel'];
106  }
107  if ( isset( $record['level'] ) ) {
108  $message['@fields']['level'] = $record['level'];
109  }
110  if ( $this->applicationName ) {
111  $message['@type'] = $this->applicationName;
112  }
113  if ( isset( $record['extra']['server'] ) ) {
114  $message['@source_host'] = $record['extra']['server'];
115  }
116  if ( isset( $record['extra']['url'] ) ) {
117  $message['@source_path'] = $record['extra']['url'];
118  }
119  if ( !empty( $record['extra'] ) ) {
120  foreach ( $record['extra'] as $key => $val ) {
121  $message['@fields'][$this->extraKey . $key] = $val;
122  }
123  }
124  if ( !empty( $record['context'] ) ) {
125  foreach ( $record['context'] as $key => $val ) {
126  $message['@fields'][$this->contextKey . $key] = $val;
127  }
128  }
129 
130  return $message;
131  }
132 
138  protected function formatV1( array $record ) {
139  if ( $this->contextKey ) {
140  return $this->formatMonologV1( $record );
141  }
142 
143  $context = !empty( $record['context'] ) ? $record['context'] : [];
144  $record['context'] = [];
145  $formatted = $this->formatMonologV1( $record );
146 
147  return $this->fixKeyConflicts( $formatted, $context );
148  }
149 
157  protected function formatMonologV1( array $record ) {
158  if ( empty( $record['datetime'] ) ) {
159  $record['datetime'] = gmdate( 'c' );
160  }
161  $message = [
162  '@timestamp' => $record['datetime'],
163  '@version' => 1,
164  'host' => $this->systemName,
165  ];
166  if ( isset( $record['message'] ) ) {
167  $message['message'] = $record['message'];
168  }
169  if ( isset( $record['channel'] ) ) {
170  $message['type'] = $record['channel'];
171  $message['channel'] = $record['channel'];
172  }
173  if ( isset( $record['level_name'] ) ) {
174  $message['level'] = $record['level_name'];
175  }
176  // level -> monolog_level is new in 2.0
177  // https://github.com/Seldaek/monolog/blob/2.0.2/src/Monolog/Formatter/LogstashFormatter.php#L86-L88
178  if ( isset( $record['level'] ) ) {
179  $message['monolog_level'] = $record['level'];
180  }
181  if ( $this->applicationName ) {
182  $message['type'] = $this->applicationName;
183  }
184  if ( !empty( $record['extra'] ) ) {
185  foreach ( $record['extra'] as $key => $val ) {
186  $message[$this->extraKey . $key] = $val;
187  }
188  }
189  if ( !empty( $record['context'] ) ) {
190  foreach ( $record['context'] as $key => $val ) {
191  $message[$this->contextKey . $key] = $val;
192  }
193  }
194 
195  return $message;
196  }
197 
205  protected function fixKeyConflicts( array $fields, array $context ) {
206  foreach ( $context as $key => $val ) {
207  if (
208  in_array( $key, $this->reservedKeys, true ) &&
209  isset( $fields[$key] ) && $fields[$key] !== $val
210  ) {
211  $fields['logstash_formatter_key_conflict'][] = $key;
212  $key = 'c_' . $key;
213  }
214  $fields[$key] = $val;
215  }
216  return $fields;
217  }
218 
225  protected function normalizeException( \Throwable $e, int $depth = 0 ) {
226  $data = [
227  'class' => get_class( $e ),
228  'message' => $e->getMessage(),
229  'code' => $e->getCode(),
230  'file' => $e->getFile() . ':' . $e->getLine(),
232  ];
233 
234  $previous = $e->getPrevious();
235  if ( $previous ) {
236  $data['previous'] = $this->normalizeException( $previous );
237  }
238 
239  return $data;
240  }
241 }
MediaWiki\Logger\Monolog\LogstashFormatter\fixKeyConflicts
fixKeyConflicts(array $fields, array $context)
Check whether some context field would overwrite another message key.
Definition: LogstashFormatter.php:205
MediaWiki\Logger\Monolog
Definition: BufferHandler.php:23
MediaWiki\Logger\Monolog\LogstashFormatter
LogstashFormatter squashes the base message array and the context and extras subarrays into one.
Definition: LogstashFormatter.php:13
MediaWiki\Logger\Monolog\LogstashFormatter\formatMonologV1
formatMonologV1(array $record)
Borrowed mostly from monolog/monolog 1.25.3 https://github.com/Seldaek/monolog/blob/1....
Definition: LogstashFormatter.php:157
MediaWiki\Logger\Monolog\LogstashFormatter\formatV0
formatV0(array $record)
Prevent key conflicts.
Definition: LogstashFormatter.php:70
MediaWiki\Logger\Monolog\LogstashFormatter\$reservedKeys
array $reservedKeys
Keys which should not be used in log context.
Definition: LogstashFormatter.php:19
MWExceptionHandler\getRedactedTraceAsString
static getRedactedTraceAsString(Throwable $e)
Generate a string representation of a throwable's stack trace.
Definition: MWExceptionHandler.php:375
MediaWiki\Logger\Monolog\LogstashFormatter\format
format(array $record)
Definition: LogstashFormatter.php:54
MediaWiki\Logger\Monolog\LogstashFormatter\formatV1
formatV1(array $record)
Prevent key conflicts.
Definition: LogstashFormatter.php:138
MediaWiki\Logger\Monolog\LogstashFormatter\__construct
__construct(string $applicationName, ?string $systemName=null, string $extraKey='', string $contextKey='ctxt_', $version=self::V0)
See T247675 for removing this override.
Definition: LogstashFormatter.php:47
MediaWiki\Logger\Monolog\LogstashFormatter\V1
const V1
Definition: LogstashFormatter.php:16
MediaWiki\Logger\Monolog\LogstashFormatter\formatMonologV0
formatMonologV0(array $record)
Borrowed from monolog/monolog 1.25.3 https://github.com/Seldaek/monolog/blob/1.x/src/Monolog/Formatte...
Definition: LogstashFormatter.php:91
MediaWiki\Logger\Monolog\LogstashFormatter\normalizeException
normalizeException(\Throwable $e, int $depth=0)
Use a more user-friendly trace format than NormalizerFormatter.
Definition: LogstashFormatter.php:225
MediaWiki\Logger\Monolog\LogstashFormatter\$version
int $version
Logstash format version to use.
Definition: LogstashFormatter.php:33
MediaWiki\$context
IContextSource $context
Definition: MediaWiki.php:40
MediaWiki\Logger\Monolog\LogstashFormatter\V0
const V0
Definition: LogstashFormatter.php:15