ErrorHandler.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. <?php declare(strict_types=1);
  2. /*
  3. * This file is part of the Monolog package.
  4. *
  5. * (c) Jordi Boggiano <j.boggiano@seld.be>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Monolog;
  11. use Psr\Log\LoggerInterface;
  12. use Psr\Log\LogLevel;
  13. /**
  14. * Monolog error handler
  15. *
  16. * A facility to enable logging of runtime errors, exceptions and fatal errors.
  17. *
  18. * Quick setup: <code>ErrorHandler::register($logger);</code>
  19. *
  20. * @author Jordi Boggiano <j.boggiano@seld.be>
  21. */
  22. class ErrorHandler
  23. {
  24. private $logger;
  25. private $previousExceptionHandler;
  26. private $uncaughtExceptionLevelMap;
  27. private $previousErrorHandler;
  28. private $errorLevelMap;
  29. private $hasFatalErrorHandler;
  30. private $fatalLevel;
  31. private $reservedMemory;
  32. private static $fatalErrors = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];
  33. public function __construct(LoggerInterface $logger)
  34. {
  35. $this->logger = $logger;
  36. }
  37. /**
  38. * Registers a new ErrorHandler for a given Logger
  39. *
  40. * By default it will handle errors, exceptions and fatal errors
  41. *
  42. * @param LoggerInterface $logger
  43. * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling
  44. * @param array|false $exceptionLevelMap an array of class name to LogLevel::* constant mapping, or false to disable exception handling
  45. * @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling
  46. * @return ErrorHandler
  47. */
  48. public static function register(LoggerInterface $logger, $errorLevelMap = [], $exceptionLevelMap = [], $fatalLevel = null)
  49. {
  50. $handler = new static($logger);
  51. if ($errorLevelMap !== false) {
  52. $handler->registerErrorHandler($errorLevelMap);
  53. }
  54. if ($exceptionLevelMap !== false) {
  55. $handler->registerExceptionHandler($exceptionLevelMap);
  56. }
  57. if ($fatalLevel !== false) {
  58. $handler->registerFatalHandler($fatalLevel);
  59. }
  60. return $handler;
  61. }
  62. public function registerExceptionHandler($levelMap = [], $callPrevious = true)
  63. {
  64. $prev = set_exception_handler([$this, 'handleException']);
  65. $this->uncaughtExceptionLevelMap = array_replace($this->defaultExceptionLevelMap(), $levelMap);
  66. if ($callPrevious && $prev) {
  67. $this->previousExceptionHandler = $prev;
  68. }
  69. }
  70. public function registerErrorHandler(array $levelMap = [], $callPrevious = true, $errorTypes = -1)
  71. {
  72. $prev = set_error_handler([$this, 'handleError'], $errorTypes);
  73. $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap);
  74. if ($callPrevious) {
  75. $this->previousErrorHandler = $prev ?: true;
  76. }
  77. }
  78. public function registerFatalHandler($level = null, $reservedMemorySize = 20)
  79. {
  80. register_shutdown_function([$this, 'handleFatalError']);
  81. $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);
  82. $this->fatalLevel = $level;
  83. $this->hasFatalErrorHandler = true;
  84. }
  85. protected function defaultExceptionLevelMap()
  86. {
  87. return [
  88. 'ParseError' => LogLevel::CRITICAL,
  89. 'Throwable' => LogLevel::ERROR,
  90. ];
  91. }
  92. protected function defaultErrorLevelMap()
  93. {
  94. return [
  95. E_ERROR => LogLevel::CRITICAL,
  96. E_WARNING => LogLevel::WARNING,
  97. E_PARSE => LogLevel::ALERT,
  98. E_NOTICE => LogLevel::NOTICE,
  99. E_CORE_ERROR => LogLevel::CRITICAL,
  100. E_CORE_WARNING => LogLevel::WARNING,
  101. E_COMPILE_ERROR => LogLevel::ALERT,
  102. E_COMPILE_WARNING => LogLevel::WARNING,
  103. E_USER_ERROR => LogLevel::ERROR,
  104. E_USER_WARNING => LogLevel::WARNING,
  105. E_USER_NOTICE => LogLevel::NOTICE,
  106. E_STRICT => LogLevel::NOTICE,
  107. E_RECOVERABLE_ERROR => LogLevel::ERROR,
  108. E_DEPRECATED => LogLevel::NOTICE,
  109. E_USER_DEPRECATED => LogLevel::NOTICE,
  110. ];
  111. }
  112. /**
  113. * @private
  114. */
  115. public function handleException($e)
  116. {
  117. $level = LogLevel::ERROR;
  118. foreach ($this->uncaughtExceptionLevelMap as $class => $candidate) {
  119. if ($e instanceof $class) {
  120. $level = $candidate;
  121. break;
  122. }
  123. }
  124. $this->logger->log(
  125. $level,
  126. sprintf('Uncaught Exception %s: "%s" at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()),
  127. ['exception' => $e]
  128. );
  129. if ($this->previousExceptionHandler) {
  130. call_user_func($this->previousExceptionHandler, $e);
  131. }
  132. exit(255);
  133. }
  134. /**
  135. * @private
  136. */
  137. public function handleError($code, $message, $file = '', $line = 0, $context = [])
  138. {
  139. if (!(error_reporting() & $code)) {
  140. return;
  141. }
  142. // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries
  143. if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) {
  144. $level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL;
  145. $this->logger->log($level, self::codeToString($code).': '.$message, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line]);
  146. }
  147. if ($this->previousErrorHandler === true) {
  148. return false;
  149. } elseif ($this->previousErrorHandler) {
  150. return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context);
  151. }
  152. }
  153. /**
  154. * @private
  155. */
  156. public function handleFatalError()
  157. {
  158. $this->reservedMemory = null;
  159. $lastError = error_get_last();
  160. if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) {
  161. $this->logger->log(
  162. $this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel,
  163. 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'],
  164. ['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line']]
  165. );
  166. if ($this->logger instanceof Logger) {
  167. foreach ($this->logger->getHandlers() as $handler) {
  168. $handler->close();
  169. }
  170. }
  171. }
  172. }
  173. private static function codeToString($code)
  174. {
  175. switch ($code) {
  176. case E_ERROR:
  177. return 'E_ERROR';
  178. case E_WARNING:
  179. return 'E_WARNING';
  180. case E_PARSE:
  181. return 'E_PARSE';
  182. case E_NOTICE:
  183. return 'E_NOTICE';
  184. case E_CORE_ERROR:
  185. return 'E_CORE_ERROR';
  186. case E_CORE_WARNING:
  187. return 'E_CORE_WARNING';
  188. case E_COMPILE_ERROR:
  189. return 'E_COMPILE_ERROR';
  190. case E_COMPILE_WARNING:
  191. return 'E_COMPILE_WARNING';
  192. case E_USER_ERROR:
  193. return 'E_USER_ERROR';
  194. case E_USER_WARNING:
  195. return 'E_USER_WARNING';
  196. case E_USER_NOTICE:
  197. return 'E_USER_NOTICE';
  198. case E_STRICT:
  199. return 'E_STRICT';
  200. case E_RECOVERABLE_ERROR:
  201. return 'E_RECOVERABLE_ERROR';
  202. case E_DEPRECATED:
  203. return 'E_DEPRECATED';
  204. case E_USER_DEPRECATED:
  205. return 'E_USER_DEPRECATED';
  206. }
  207. return 'Unknown PHP error';
  208. }
  209. }