MediaWiki master
LogstashFormatter.php
Go to the documentation of this file.
1<?php
2
4
6
17class LogstashFormatter extends \Monolog\Formatter\LogstashFormatter {
18
19 public const V0 = 0;
20 public const V1 = 1;
21
23 protected $reservedKeys = [
24 // from LogstashFormatter
25 'message', 'channel', 'level', 'type',
26 // from WebProcessor
27 'url', 'ip', 'http_method', 'server', 'referrer',
28 // from WikiProcessor
29 'host', 'wiki', 'reqId', 'mwversion',
30 // from config magic
31 'normalized_message',
32 ];
33
37 protected $version;
38
51 public function __construct( string $applicationName, ?string $systemName = null,
52 string $extraKey = '', string $contextKey = 'ctxt_', $version = self::V0
53 ) {
54 $this->version = $version;
55 parent::__construct( $applicationName, $systemName, $extraKey, $contextKey );
56 }
57
58 public function format( array $record ): string {
59 $record = \Monolog\Formatter\NormalizerFormatter::format( $record );
60 if ( $this->version === self::V1 ) {
61 $message = $this->formatV1( $record );
62 } elseif ( $this->version === self::V0 ) {
63 $message = $this->formatV0( $record );
64 } else {
65 $message = __METHOD__ . ' unknown version ' . $this->version;
66 }
67
68 return $this->toJson( $message ) . "\n";
69 }
70
76 protected function formatV0( array $record ) {
77 if ( $this->contextKey !== '' ) {
78 return $this->formatMonologV0( $record );
79 }
80
81 $context = !empty( $record['context'] ) ? $record['context'] : [];
82 $record['context'] = [];
83 $formatted = $this->formatMonologV0( $record );
84
85 $formatted['@fields'] = $this->fixKeyConflicts( $formatted['@fields'], $context );
86
87 return $formatted;
88 }
89
97 protected function formatMonologV0( array $record ) {
98 if ( empty( $record['datetime'] ) ) {
99 $record['datetime'] = gmdate( 'c' );
100 }
101 $message = [
102 '@timestamp' => $record['datetime'],
103 '@source' => $this->systemName,
104 '@fields' => [],
105 ];
106 if ( isset( $record['message'] ) ) {
107 $message['@message'] = $record['message'];
108 }
109 if ( isset( $record['channel'] ) ) {
110 $message['@tags'] = [ $record['channel'] ];
111 $message['@fields']['channel'] = $record['channel'];
112 }
113 if ( isset( $record['level'] ) ) {
114 $message['@fields']['level'] = $record['level'];
115 }
116 if ( $this->applicationName ) {
117 $message['@type'] = $this->applicationName;
118 }
119 if ( isset( $record['extra']['server'] ) ) {
120 $message['@source_host'] = $record['extra']['server'];
121 }
122 if ( isset( $record['extra']['url'] ) ) {
123 $message['@source_path'] = $record['extra']['url'];
124 }
125 if ( !empty( $record['extra'] ) ) {
126 foreach ( $record['extra'] as $key => $val ) {
127 $message['@fields'][$this->extraKey . $key] = $val;
128 }
129 }
130 if ( !empty( $record['context'] ) ) {
131 foreach ( $record['context'] as $key => $val ) {
132 $message['@fields'][$this->contextKey . $key] = $val;
133 }
134 }
135
136 return $message;
137 }
138
144 protected function formatV1( array $record ) {
145 if ( $this->contextKey ) {
146 return $this->formatMonologV1( $record );
147 }
148
149 $context = !empty( $record['context'] ) ? $record['context'] : [];
150 $record['context'] = [];
151 $formatted = $this->formatMonologV1( $record );
152
153 return $this->fixKeyConflicts( $formatted, $context );
154 }
155
163 protected function formatMonologV1( array $record ) {
164 if ( empty( $record['datetime'] ) ) {
165 $record['datetime'] = gmdate( 'c' );
166 }
167 $message = [
168 '@timestamp' => $record['datetime'],
169 '@version' => 1,
170 'host' => $this->systemName,
171 ];
172 if ( isset( $record['message'] ) ) {
173 $message['message'] = $record['message'];
174 }
175 if ( isset( $record['channel'] ) ) {
176 $message['type'] = $record['channel'];
177 $message['channel'] = $record['channel'];
178 }
179 if ( isset( $record['level_name'] ) ) {
180 $message['level'] = $record['level_name'];
181 }
182 // level -> monolog_level is new in 2.0
183 // https://github.com/Seldaek/monolog/blob/2.0.2/src/Monolog/Formatter/LogstashFormatter.php#L86-L88
184 if ( isset( $record['level'] ) ) {
185 $message['monolog_level'] = $record['level'];
186 }
187 if ( $this->applicationName ) {
188 $message['type'] = $this->applicationName;
189 }
190 if ( !empty( $record['extra'] ) ) {
191 foreach ( $record['extra'] as $key => $val ) {
192 $message[$this->extraKey . $key] = $val;
193 }
194 }
195 if ( !empty( $record['context'] ) ) {
196 foreach ( $record['context'] as $key => $val ) {
197 $message[$this->contextKey . $key] = $val;
198 }
199 }
200
201 return $message;
202 }
203
211 protected function fixKeyConflicts( array $fields, array $context ) {
212 foreach ( $context as $key => $val ) {
213 if (
214 in_array( $key, $this->reservedKeys, true ) &&
215 isset( $fields[$key] ) && $fields[$key] !== $val
216 ) {
217 $fields['logstash_formatter_key_conflict'][] = $key;
218 $key = 'c_' . $key;
219 }
220 $fields[$key] = $val;
221 }
222 return $fields;
223 }
224
232 protected function normalizeException( \Throwable $e, int $depth = 0 ) {
233 $data = [
234 'class' => get_class( $e ),
235 'message' => $e->getMessage(),
236 'code' => $e->getCode(),
237 'file' => $e->getFile() . ':' . $e->getLine(),
238 'trace' => MWExceptionHandler::getRedactedTraceAsString( $e ),
239 ];
240
241 $previous = $e->getPrevious();
242 if ( $previous ) {
243 $data['previous'] = $this->normalizeException( $previous );
244 }
245
246 return $data;
247 }
248}
Handler class for MWExceptions.
Modified version of Monolog\Formatter\LogstashFormatter.
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.