e6e7898336a474c24475d6e023d47a8e96153396
[friendica-addons.git/.git] / monolog / vendor / monolog / monolog / src / Monolog / Formatter / LineFormatter.php
1 <?php declare(strict_types=1);
2
3 /*
4  * This file is part of the Monolog package.
5  *
6  * (c) Jordi Boggiano <j.boggiano@seld.be>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Monolog\Formatter;
13
14 use Monolog\Utils;
15
16 /**
17  * Formats incoming records into a one-line string
18  *
19  * This is especially useful for logging to files
20  *
21  * @author Jordi Boggiano <j.boggiano@seld.be>
22  * @author Christophe Coevoet <stof@notk.org>
23  */
24 class LineFormatter extends NormalizerFormatter
25 {
26     public const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n";
27
28     /** @var string */
29     protected $format;
30     /** @var bool */
31     protected $allowInlineLineBreaks;
32     /** @var bool */
33     protected $ignoreEmptyContextAndExtra;
34     /** @var bool */
35     protected $includeStacktraces;
36     /** @var ?callable */
37     protected $stacktracesParser;
38
39     /**
40      * @param string|null $format                     The format of the message
41      * @param string|null $dateFormat                 The format of the timestamp: one supported by DateTime::format
42      * @param bool        $allowInlineLineBreaks      Whether to allow inline line breaks in log entries
43      * @param bool        $ignoreEmptyContextAndExtra
44      */
45     public function __construct(?string $format = null, ?string $dateFormat = null, bool $allowInlineLineBreaks = false, bool $ignoreEmptyContextAndExtra = false, bool $includeStacktraces = false)
46     {
47         $this->format = $format === null ? static::SIMPLE_FORMAT : $format;
48         $this->allowInlineLineBreaks = $allowInlineLineBreaks;
49         $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra;
50         $this->includeStacktraces($includeStacktraces);
51         parent::__construct($dateFormat);
52     }
53
54     public function includeStacktraces(bool $include = true, ?callable $parser = null): self
55     {
56         $this->includeStacktraces = $include;
57         if ($this->includeStacktraces) {
58             $this->allowInlineLineBreaks = true;
59             $this->stacktracesParser = $parser;
60         }
61
62         return $this;
63     }
64
65     public function allowInlineLineBreaks(bool $allow = true): self
66     {
67         $this->allowInlineLineBreaks = $allow;
68
69         return $this;
70     }
71
72     public function ignoreEmptyContextAndExtra(bool $ignore = true): self
73     {
74         $this->ignoreEmptyContextAndExtra = $ignore;
75
76         return $this;
77     }
78
79     /**
80      * {@inheritDoc}
81      */
82     public function format(array $record): string
83     {
84         $vars = parent::format($record);
85
86         $output = $this->format;
87
88         foreach ($vars['extra'] as $var => $val) {
89             if (false !== strpos($output, '%extra.'.$var.'%')) {
90                 $output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output);
91                 unset($vars['extra'][$var]);
92             }
93         }
94
95         foreach ($vars['context'] as $var => $val) {
96             if (false !== strpos($output, '%context.'.$var.'%')) {
97                 $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output);
98                 unset($vars['context'][$var]);
99             }
100         }
101
102         if ($this->ignoreEmptyContextAndExtra) {
103             if (empty($vars['context'])) {
104                 unset($vars['context']);
105                 $output = str_replace('%context%', '', $output);
106             }
107
108             if (empty($vars['extra'])) {
109                 unset($vars['extra']);
110                 $output = str_replace('%extra%', '', $output);
111             }
112         }
113
114         foreach ($vars as $var => $val) {
115             if (false !== strpos($output, '%'.$var.'%')) {
116                 $output = str_replace('%'.$var.'%', $this->stringify($val), $output);
117             }
118         }
119
120         // remove leftover %extra.xxx% and %context.xxx% if any
121         if (false !== strpos($output, '%')) {
122             $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output);
123             if (null === $output) {
124                 $pcreErrorCode = preg_last_error();
125                 throw new \RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode));
126             }
127         }
128
129         return $output;
130     }
131
132     public function formatBatch(array $records): string
133     {
134         $message = '';
135         foreach ($records as $record) {
136             $message .= $this->format($record);
137         }
138
139         return $message;
140     }
141
142     /**
143      * @param mixed $value
144      */
145     public function stringify($value): string
146     {
147         return $this->replaceNewlines($this->convertToString($value));
148     }
149
150     protected function normalizeException(\Throwable $e, int $depth = 0): string
151     {
152         $str = $this->formatException($e);
153
154         if ($previous = $e->getPrevious()) {
155             do {
156                 $depth++;
157                 if ($depth > $this->maxNormalizeDepth) {
158                     $str .= "\n[previous exception] Over " . $this->maxNormalizeDepth . ' levels deep, aborting normalization';
159                     break;
160                 }
161
162                 $str .= "\n[previous exception] " . $this->formatException($previous);
163             } while ($previous = $previous->getPrevious());
164         }
165
166         return $str;
167     }
168
169     /**
170      * @param mixed $data
171      */
172     protected function convertToString($data): string
173     {
174         if (null === $data || is_bool($data)) {
175             return var_export($data, true);
176         }
177
178         if (is_scalar($data)) {
179             return (string) $data;
180         }
181
182         return $this->toJson($data, true);
183     }
184
185     protected function replaceNewlines(string $str): string
186     {
187         if ($this->allowInlineLineBreaks) {
188             if (0 === strpos($str, '{')) {
189                 $str = preg_replace('/(?<!\\\\)\\\\[rn]/', "\n", $str);
190                 if (null === $str) {
191                     $pcreErrorCode = preg_last_error();
192                     throw new \RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode));
193                 }
194             }
195
196             return $str;
197         }
198
199         return str_replace(["\r\n", "\r", "\n"], ' ', $str);
200     }
201
202     private function formatException(\Throwable $e): string
203     {
204         $str = '[object] (' . Utils::getClass($e) . '(code: ' . $e->getCode();
205         if ($e instanceof \SoapFault) {
206             if (isset($e->faultcode)) {
207                 $str .= ' faultcode: ' . $e->faultcode;
208             }
209
210             if (isset($e->faultactor)) {
211                 $str .= ' faultactor: ' . $e->faultactor;
212             }
213
214             if (isset($e->detail)) {
215                 if (is_string($e->detail)) {
216                     $str .= ' detail: ' . $e->detail;
217                 } elseif (is_object($e->detail) || is_array($e->detail)) {
218                     $str .= ' detail: ' . $this->toJson($e->detail, true);
219                 }
220             }
221         }
222         $str .= '): ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . ')';
223
224         if ($this->includeStacktraces) {
225             $str .= $this->stacktracesParser($e);
226         }
227
228         return $str;
229     }
230
231     private function stacktracesParser(\Throwable $e): string
232     {
233         $trace = $e->getTraceAsString();
234
235         if ($this->stacktracesParser) {
236             $trace = $this->stacktracesParserCustom($trace);
237         }
238
239         return "\n[stacktrace]\n" . $trace . "\n";
240     }
241
242     private function stacktracesParserCustom(string $trace): string
243     {
244         return implode("\n", array_filter(array_map($this->stacktracesParser, explode("\n", $trace))));
245     }
246 }