ErrorHandler.php 7.9 KB

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