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