ErrorHandler.php 7.4 KB

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