MediaWiki REL1_40
LogstashFormatter.php
Go to the documentation of this file.
1<?php
2
4
13class 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 } else {
61 $message = __METHOD__ . ' unknown version ' . $this->version;
62 }
63
64 return $this->toJson( $message ) . "\n";
65 }
66
72 protected function formatV0( array $record ) {
73 if ( $this->contextKey !== '' ) {
74 return $this->formatMonologV0( $record );
75 }
76
77 $context = !empty( $record['context'] ) ? $record['context'] : [];
78 $record['context'] = [];
79 $formatted = $this->formatMonologV0( $record );
80
81 $formatted['@fields'] = $this->fixKeyConflicts( $formatted['@fields'], $context );
82
83 return $formatted;
84 }
85
93 protected function formatMonologV0( array $record ) {
94 if ( empty( $record['datetime'] ) ) {
95 $record['datetime'] = gmdate( 'c' );
96 }
97 $message = [
98 '@timestamp' => $record['datetime'],
99 '@source' => $this->systemName,
100 '@fields' => [],
101 ];
102 if ( isset( $record['message'] ) ) {
103 $message['@message'] = $record['message'];
104 }
105 if ( isset( $record['channel'] ) ) {
106 $message['@tags'] = [ $record['channel'] ];
107 $message['@fields']['channel'] = $record['channel'];
108 }
109 if ( isset( $record['level'] ) ) {
110 $message['@fields']['level'] = $record['level'];
111 }
112 if ( $this->applicationName ) {
113 $message['@type'] = $this->applicationName;
114 }
115 if ( isset( $record['extra']['server'] ) ) {
116 $message['@source_host'] = $record['extra']['server'];
117 }
118 if ( isset( $record['extra']['url'] ) ) {
119 $message['@source_path'] = $record['extra']['url'];
120 }
121 if ( !empty( $record['extra'] ) ) {
122 foreach ( $record['extra'] as $key => $val ) {
123 $message['@fields'][$this->extraKey . $key] = $val;
124 }
125 }
126 if ( !empty( $record['context'] ) ) {
127 foreach ( $record['context'] as $key => $val ) {
128 $message['@fields'][$this->contextKey . $key] = $val;
129 }
130 }
131
132 return $message;
133 }
134
140 protected function formatV1( array $record ) {
141 if ( $this->contextKey ) {
142 return $this->formatMonologV1( $record );
143 }
144
145 $context = !empty( $record['context'] ) ? $record['context'] : [];
146 $record['context'] = [];
147 $formatted = $this->formatMonologV1( $record );
148
149 return $this->fixKeyConflicts( $formatted, $context );
150 }
151
159 protected function formatMonologV1( array $record ) {
160 if ( empty( $record['datetime'] ) ) {
161 $record['datetime'] = gmdate( 'c' );
162 }
163 $message = [
164 '@timestamp' => $record['datetime'],
165 '@version' => 1,
166 'host' => $this->systemName,
167 ];
168 if ( isset( $record['message'] ) ) {
169 $message['message'] = $record['message'];
170 }
171 if ( isset( $record['channel'] ) ) {
172 $message['type'] = $record['channel'];
173 $message['channel'] = $record['channel'];
174 }
175 if ( isset( $record['level_name'] ) ) {
176 $message['level'] = $record['level_name'];
177 }
178 // level -> monolog_level is new in 2.0
179 // https://github.com/Seldaek/monolog/blob/2.0.2/src/Monolog/Formatter/LogstashFormatter.php#L86-L88
180 if ( isset( $record['level'] ) ) {
181 $message['monolog_level'] = $record['level'];
182 }
183 if ( $this->applicationName ) {
184 $message['type'] = $this->applicationName;
185 }
186 if ( !empty( $record['extra'] ) ) {
187 foreach ( $record['extra'] as $key => $val ) {
188 $message[$this->extraKey . $key] = $val;
189 }
190 }
191 if ( !empty( $record['context'] ) ) {
192 foreach ( $record['context'] as $key => $val ) {
193 $message[$this->contextKey . $key] = $val;
194 }
195 }
196
197 return $message;
198 }
199
207 protected function fixKeyConflicts( array $fields, array $context ) {
208 foreach ( $context as $key => $val ) {
209 if (
210 in_array( $key, $this->reservedKeys, true ) &&
211 isset( $fields[$key] ) && $fields[$key] !== $val
212 ) {
213 $fields['logstash_formatter_key_conflict'][] = $key;
214 $key = 'c_' . $key;
215 }
216 $fields[$key] = $val;
217 }
218 return $fields;
219 }
220
228 protected function normalizeException( \Throwable $e, int $depth = 0 ) {
229 $data = [
230 'class' => get_class( $e ),
231 'message' => $e->getMessage(),
232 'code' => $e->getCode(),
233 'file' => $e->getFile() . ':' . $e->getLine(),
234 'trace' => \MWExceptionHandler::getRedactedTraceAsString( $e ),
235 ];
236
237 $previous = $e->getPrevious();
238 if ( $previous ) {
239 $data['previous'] = $this->normalizeException( $previous );
240 }
241
242 return $data;
243 }
244}
LogstashFormatter squashes the base message array and the context and extras subarrays into one.
formatV1(array $record)
Prevent key conflicts.
__construct(string $applicationName, ?string $systemName=null, string $extraKey='', string $contextKey='ctxt_', $version=self::V0)
TODO: See T247675 for removing this override.
formatMonologV0(array $record)
Borrowed from monolog/monolog 1.25.3 https://github.com/Seldaek/monolog/blob/1.x/src/Monolog/Formatte...
normalizeException(\Throwable $e, int $depth=0)
Use a more user-friendly trace format than Monolog\Formatter\NormalizerFormatter.
fixKeyConflicts(array $fields, array $context)
Rename any context field that would otherwise overwrite a message key.
formatV0(array $record)
Prevent key conflicts.
formatMonologV1(array $record)
Borrowed mostly from monolog/monolog 1.25.3 https://github.com/Seldaek/monolog/blob/1....
array $reservedKeys
Keys which should not be used in log context.
int $version
Logstash format version to use.