source: vendor/monolog/monolog/src/Monolog/ErrorHandler.php@ e3d4e0a

Last change on this file since e3d4e0a was e3d4e0a, checked in by Vlado 222039 <vlado.popovski@…>, 7 days ago

Upload project files

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