PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / trunk
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance vtrunk
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / vendor / monolog / monolog / src / Monolog / ErrorHandler.php

ErrorHandler.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance trunk, at vendor/monolog/monolog/src/Monolog/ErrorHandler.php

302 lines 10.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php declare(strict_types=1);
2
3 /*
4 * This file is part of the Monolog package.
5 *
6 * (c) Jordi Boggiano <[email protected]>
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;
13
14 use Psr\Log\LoggerInterface;
15 use Psr\Log\LogLevel;
16
17 /**
18 * Monolog error handler
19 *
20 * A facility to enable logging of runtime errors, exceptions and fatal errors.
21 *
22 * Quick setup: <code>ErrorHandler::register($logger);</code>
23 *
24 * @author Jordi Boggiano <[email protected]>
25 */
26 class ErrorHandler
27 {
28 /** @var LoggerInterface */
29 private $logger;
30
31 /** @var ?callable */
32 private $previousExceptionHandler = null;
33 /** @var array<class-string, LogLevel::*> an array of class name to LogLevel::* constant mapping */
34 private $uncaughtExceptionLevelMap = [];
35
36 /** @var callable|true|null */
37 private $previousErrorHandler = null;
38 /** @var array<int, LogLevel::*> an array of E_* constant to LogLevel::* constant mapping */
39 private $errorLevelMap = [];
40 /** @var bool */
41 private $handleOnlyReportedErrors = true;
42
43 /** @var bool */
44 private $hasFatalErrorHandler = false;
45 /** @var LogLevel::* */
46 private $fatalLevel = LogLevel::ALERT;
47 /** @var ?string */
48 private $reservedMemory = null;
49 /** @var ?mixed */
50 private $lastFatalTrace;
51 /** @var int[] */
52 private static $fatalErrors = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];
53
54 public function __construct(LoggerInterface $logger)
55 {
56 $this->logger = $logger;
57 }
58
59 /**
60 * Registers a new ErrorHandler for a given Logger
61 *
62 * By default it will handle errors, exceptions and fatal errors
63 *
64 * @param LoggerInterface $logger
65 * @param array<int, LogLevel::*>|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling
66 * @param array<class-string, LogLevel::*>|false $exceptionLevelMap an array of class name to LogLevel::* constant mapping, or false to disable exception handling
67 * @param LogLevel::*|null|false $fatalLevel a LogLevel::* constant, null to use the default LogLevel::ALERT or false to disable fatal error handling
68 * @return ErrorHandler
69 */
70 public static function register(LoggerInterface $logger, $errorLevelMap = [], $exceptionLevelMap = [], $fatalLevel = null): self
71 {
72 /** @phpstan-ignore-next-line */
73 $handler = new static($logger);
74 if ($errorLevelMap !== false) {
75 $handler->registerErrorHandler($errorLevelMap);
76 }
77 if ($exceptionLevelMap !== false) {
78 $handler->registerExceptionHandler($exceptionLevelMap);
79 }
80 if ($fatalLevel !== false) {
81 $handler->registerFatalHandler($fatalLevel);
82 }
83
84 return $handler;
85 }
86
87 /**
88 * @param array<class-string, LogLevel::*> $levelMap an array of class name to LogLevel::* constant mapping
89 * @return $this
90 */
91 public function registerExceptionHandler(array $levelMap = [], bool $callPrevious = true): self
92 {
93 $prev = set_exception_handler(function (\Throwable $e): void {
94 $this->handleException($e);
95 });
96 $this->uncaughtExceptionLevelMap = $levelMap;
97 foreach ($this->defaultExceptionLevelMap() as $class => $level) {
98 if (!isset($this->uncaughtExceptionLevelMap[$class])) {
99 $this->uncaughtExceptionLevelMap[$class] = $level;
100 }
101 }
102 if ($callPrevious && $prev) {
103 $this->previousExceptionHandler = $prev;
104 }
105
106 return $this;
107 }
108
109 /**
110 * @param array<int, LogLevel::*> $levelMap an array of E_* constant to LogLevel::* constant mapping
111 * @return $this
112 */
113 public function registerErrorHandler(array $levelMap = [], bool $callPrevious = true, int $errorTypes = -1, bool $handleOnlyReportedErrors = true): self
114 {
115 $prev = set_error_handler([$this, 'handleError'], $errorTypes);
116 $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap);
117 if ($callPrevious) {
118 $this->previousErrorHandler = $prev ?: true;
119 } else {
120 $this->previousErrorHandler = null;
121 }
122
123 $this->handleOnlyReportedErrors = $handleOnlyReportedErrors;
124
125 return $this;
126 }
127
128 /**
129 * @param LogLevel::*|null $level a LogLevel::* constant, null to use the default LogLevel::ALERT
130 * @param int $reservedMemorySize Amount of KBs to reserve in memory so that it can be freed when handling fatal errors giving Monolog some room in memory to get its job done
131 */
132 public function registerFatalHandler($level = null, int $reservedMemorySize = 20): self
133 {
134 register_shutdown_function([$this, 'handleFatalError']);
135
136 $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);
137 $this->fatalLevel = null === $level ? LogLevel::ALERT : $level;
138 $this->hasFatalErrorHandler = true;
139
140 return $this;
141 }
142
143 /**
144 * @return array<class-string, LogLevel::*>
145 */
146 protected function defaultExceptionLevelMap(): array
147 {
148 return [
149 'ParseError' => LogLevel::CRITICAL,
150 'Throwable' => LogLevel::ERROR,
151 ];
152 }
153
154 /**
155 * @return array<int, LogLevel::*>
156 */
157 protected function defaultErrorLevelMap(): array
158 {
159 return [
160 E_ERROR => LogLevel::CRITICAL,
161 E_WARNING => LogLevel::WARNING,
162 E_PARSE => LogLevel::ALERT,
163 E_NOTICE => LogLevel::NOTICE,
164 E_CORE_ERROR => LogLevel::CRITICAL,
165 E_CORE_WARNING => LogLevel::WARNING,
166 E_COMPILE_ERROR => LogLevel::ALERT,
167 E_COMPILE_WARNING => LogLevel::WARNING,
168 E_USER_ERROR => LogLevel::ERROR,
169 E_USER_WARNING => LogLevel::WARNING,
170 E_USER_NOTICE => LogLevel::NOTICE,
171 E_STRICT => LogLevel::NOTICE,
172 E_RECOVERABLE_ERROR => LogLevel::ERROR,
173 E_DEPRECATED => LogLevel::NOTICE,
174 E_USER_DEPRECATED => LogLevel::NOTICE,
175 ];
176 }
177
178 /**
179 * @phpstan-return never
180 */
181 private function handleException(\Throwable $e): void
182 {
183 $level = LogLevel::ERROR;
184 foreach ($this->uncaughtExceptionLevelMap as $class => $candidate) {
185 if ($e instanceof $class) {
186 $level = $candidate;
187 break;
188 }
189 }
190
191 $this->logger->log(
192 $level,
193 sprintf('Uncaught Exception %s: "%s" at %s line %s', Utils::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()),
194 ['exception' => $e]
195 );
196
197 if ($this->previousExceptionHandler) {
198 ($this->previousExceptionHandler)($e);
199 }
200
201 if (!headers_sent() && !ini_get('display_errors')) {
202 http_response_code(500);
203 }
204
205 exit(255);
206 }
207
208 /**
209 * @private
210 *
211 * @param mixed[] $context
212 */
213 public function handleError(int $code, string $message, string $file = '', int $line = 0, array $context = []): bool
214 {
215 if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) {
216 return false;
217 }
218
219 // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries
220 if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) {
221 $level = $this->errorLevelMap[$code] ?? LogLevel::CRITICAL;
222 $this->logger->log($level, self::codeToString($code).': '.$message, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line]);
223 } else {
224 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
225 array_shift($trace); // Exclude handleError from trace
226 $this->lastFatalTrace = $trace;
227 }
228
229 if ($this->previousErrorHandler === true) {
230 return false;
231 } elseif ($this->previousErrorHandler) {
232 return (bool) ($this->previousErrorHandler)($code, $message, $file, $line, $context);
233 }
234
235 return true;
236 }
237
238 /**
239 * @private
240 */
241 public function handleFatalError(): void
242 {
243 $this->reservedMemory = '';
244
245 $lastError = error_get_last();
246 if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) {
247 $this->logger->log(
248 $this->fatalLevel,
249 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'],
250 ['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $this->lastFatalTrace]
251 );
252
253 if ($this->logger instanceof Logger) {
254 foreach ($this->logger->getHandlers() as $handler) {
255 $handler->close();
256 }
257 }
258 }
259 }
260
261 /**
262 * @param int $code
263 */
264 private static function codeToString($code): string
265 {
266 switch ($code) {
267 case E_ERROR:
268 return 'E_ERROR';
269 case E_WARNING:
270 return 'E_WARNING';
271 case E_PARSE:
272 return 'E_PARSE';
273 case E_NOTICE:
274 return 'E_NOTICE';
275 case E_CORE_ERROR:
276 return 'E_CORE_ERROR';
277 case E_CORE_WARNING:
278 return 'E_CORE_WARNING';
279 case E_COMPILE_ERROR:
280 return 'E_COMPILE_ERROR';
281 case E_COMPILE_WARNING:
282 return 'E_COMPILE_WARNING';
283 case E_USER_ERROR:
284 return 'E_USER_ERROR';
285 case E_USER_WARNING:
286 return 'E_USER_WARNING';
287 case E_USER_NOTICE:
288 return 'E_USER_NOTICE';
289 case E_STRICT:
290 return 'E_STRICT';
291 case E_RECOVERABLE_ERROR:
292 return 'E_RECOVERABLE_ERROR';
293 case E_DEPRECATED:
294 return 'E_DEPRECATED';
295 case E_USER_DEPRECATED:
296 return 'E_USER_DEPRECATED';
297 }
298
299 return 'Unknown PHP error';
300 }
301 }
302