Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
1.64% |
1 / 61 |
|
20.00% |
1 / 5 |
CRAP | |
0.00% |
0 / 1 |
LineFormatter | |
1.64% |
1 / 61 |
|
20.00% |
1 / 5 |
362.54 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
format | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
42 | |||
normalizeException | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
exceptionAsArray | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 | |||
normalizeExceptionArray | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
90 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | */ |
20 | |
21 | namespace MediaWiki\Logger\Monolog; |
22 | |
23 | use Error; |
24 | use Monolog\Formatter\LineFormatter as MonologLineFormatter; |
25 | use MWExceptionHandler; |
26 | use Throwable; |
27 | |
28 | /** |
29 | * Formats incoming records into a one-line string. |
30 | * |
31 | * An 'exeception' in the log record's context will be treated specially. |
32 | * It will be output for an '%exception%' placeholder in the format and |
33 | * excluded from '%context%' output if the '%exception%' placeholder is |
34 | * present. |
35 | * |
36 | * Throwables that are logged with this formatter will optional have their |
37 | * stack traces appended. If that is done, MWExceptionHandler::redactedTrace() |
38 | * will be used to redact the trace information. |
39 | * |
40 | * @since 1.26 |
41 | * @ingroup Debug |
42 | * @copyright © 2015 Wikimedia Foundation and contributors |
43 | */ |
44 | class LineFormatter extends MonologLineFormatter { |
45 | |
46 | /** |
47 | * @param string|null $format The format of the message |
48 | * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format |
49 | * @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries |
50 | * @param bool $ignoreEmptyContextAndExtra |
51 | * @param bool $includeStacktraces |
52 | */ |
53 | public function __construct( |
54 | $format = null, $dateFormat = null, $allowInlineLineBreaks = false, |
55 | $ignoreEmptyContextAndExtra = false, $includeStacktraces = false |
56 | ) { |
57 | parent::__construct( |
58 | $format, $dateFormat, $allowInlineLineBreaks, |
59 | $ignoreEmptyContextAndExtra |
60 | ); |
61 | $this->includeStacktraces( $includeStacktraces ); |
62 | } |
63 | |
64 | /** |
65 | * @inheritDoc |
66 | */ |
67 | public function format( array $record ): string { |
68 | // Drop the 'private' flag from the context |
69 | unset( $record['context']['private'] ); |
70 | |
71 | // Handle throwables specially: pretty format and remove from context |
72 | // Will be output for a '%exception%' placeholder in format |
73 | $prettyException = ''; |
74 | if ( isset( $record['context']['exception'] ) && |
75 | str_contains( $this->format, '%exception%' ) |
76 | ) { |
77 | $e = $record['context']['exception']; |
78 | unset( $record['context']['exception'] ); |
79 | |
80 | if ( $e instanceof Throwable ) { |
81 | $prettyException = $this->normalizeException( $e ); |
82 | } elseif ( is_array( $e ) ) { |
83 | $prettyException = $this->normalizeExceptionArray( $e ); |
84 | } else { |
85 | $prettyException = $this->stringify( $e ); |
86 | } |
87 | } |
88 | |
89 | $output = parent::format( $record ); |
90 | |
91 | if ( str_contains( $output, '%exception%' ) ) { |
92 | $output = str_replace( '%exception%', $prettyException, $output ); |
93 | } |
94 | return $output; |
95 | } |
96 | |
97 | /** |
98 | * Convert a Throwable to a string. |
99 | * |
100 | * @param Throwable $e |
101 | * @param int $depth |
102 | * @return string |
103 | */ |
104 | protected function normalizeException( Throwable $e, int $depth = 0 ): string { |
105 | // Can't use typehint. Must match Monolog\Formatter\LineFormatter::normalizeException($e) |
106 | return $this->normalizeExceptionArray( $this->exceptionAsArray( $e ) ); |
107 | } |
108 | |
109 | /** |
110 | * Convert a throwable to an array of structured data. |
111 | * |
112 | * @param Throwable $e |
113 | * @return array |
114 | */ |
115 | protected function exceptionAsArray( Throwable $e ) { |
116 | $out = [ |
117 | 'class' => get_class( $e ), |
118 | 'message' => $e->getMessage(), |
119 | 'code' => $e->getCode(), |
120 | 'file' => $e->getFile(), |
121 | 'line' => $e->getLine(), |
122 | 'trace' => MWExceptionHandler::redactTrace( $e->getTrace() ), |
123 | ]; |
124 | |
125 | $prev = $e->getPrevious(); |
126 | if ( $prev ) { |
127 | $out['previous'] = $this->exceptionAsArray( $prev ); |
128 | } |
129 | |
130 | return $out; |
131 | } |
132 | |
133 | /** |
134 | * Convert an array of Throwable data to a string. |
135 | * |
136 | * @param array $e |
137 | * @return string |
138 | */ |
139 | protected function normalizeExceptionArray( array $e ) { |
140 | $defaults = [ |
141 | 'class' => 'Unknown', |
142 | 'file' => 'unknown', |
143 | 'line' => null, |
144 | 'message' => 'unknown', |
145 | 'trace' => [], |
146 | ]; |
147 | $e = array_merge( $defaults, $e ); |
148 | |
149 | // @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal class is always set |
150 | $which = is_a( $e['class'], Error::class, true ) ? 'Error' : 'Exception'; |
151 | $str = "\n[$which {$e['class']}] (" . |
152 | "{$e['file']}:{$e['line']}) {$e['message']}"; |
153 | |
154 | if ( $this->includeStacktraces && $e['trace'] ) { |
155 | $str .= "\n" . |
156 | // @phan-suppress-next-line PhanTypeMismatchArgumentNullable trace is always set |
157 | MWExceptionHandler::prettyPrintTrace( $e['trace'], ' ' ); |
158 | } |
159 | |
160 | if ( isset( $e['previous'] ) ) { |
161 | $prev = $e['previous']; |
162 | while ( $prev ) { |
163 | $prev = array_merge( $defaults, $prev ); |
164 | $which = is_a( $prev['class'], Error::class, true ) ? 'Error' : 'Exception'; |
165 | $str .= "\nCaused by: [$which {$prev['class']}] (" . |
166 | "{$prev['file']}:{$prev['line']}) {$prev['message']}"; |
167 | |
168 | if ( $this->includeStacktraces && $prev['trace'] ) { |
169 | $str .= "\n" . |
170 | MWExceptionHandler::prettyPrintTrace( |
171 | $prev['trace'], ' ' |
172 | ); |
173 | } |
174 | |
175 | $prev = $prev['previous'] ?? null; |
176 | } |
177 | } |
178 | return $str; |
179 | } |
180 | } |