ErrorHandler.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  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. use ReflectionExtension;
  15. /**
  16. * Monolog error handler
  17. *
  18. * A facility to enable logging of runtime errors, exceptions, fatal errors and signals.
  19. *
  20. * Quick setup: <code>ErrorHandler::register($logger);</code>
  21. *
  22. * @author Jordi Boggiano <j.boggiano@seld.be>
  23. */
  24. class ErrorHandler
  25. {
  26. private $logger;
  27. private $previousExceptionHandler;
  28. private $uncaughtExceptionLevel;
  29. private $previousErrorHandler;
  30. private $errorLevelMap;
  31. private $handleOnlyReportedErrors;
  32. private $hasFatalErrorHandler;
  33. private $fatalLevel;
  34. private $reservedMemory;
  35. private $lastFatalTrace;
  36. private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR);
  37. private $previousSignalHandler = array();
  38. private $signalLevelMap = array();
  39. private $signalRestartSyscalls = array();
  40. public function __construct(LoggerInterface $logger)
  41. {
  42. $this->logger = $logger;
  43. }
  44. /**
  45. * Registers a new ErrorHandler for a given Logger
  46. *
  47. * By default it will handle errors, exceptions and fatal errors
  48. *
  49. * @param LoggerInterface $logger
  50. * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling
  51. * @param int|false $exceptionLevel a LogLevel::* constant, or false to disable exception handling
  52. * @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling
  53. * @return ErrorHandler
  54. */
  55. public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null)
  56. {
  57. //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
  58. class_exists('\\Psr\\Log\\LogLevel', true);
  59. $handler = new static($logger);
  60. if ($errorLevelMap !== false) {
  61. $handler->registerErrorHandler($errorLevelMap);
  62. }
  63. if ($exceptionLevel !== false) {
  64. $handler->registerExceptionHandler($exceptionLevel);
  65. }
  66. if ($fatalLevel !== false) {
  67. $handler->registerFatalHandler($fatalLevel);
  68. }
  69. return $handler;
  70. }
  71. public function registerExceptionHandler($level = null, $callPrevious = true)
  72. {
  73. $prev = set_exception_handler(array($this, 'handleException'));
  74. $this->uncaughtExceptionLevel = $level;
  75. if ($callPrevious && $prev) {
  76. $this->previousExceptionHandler = $prev;
  77. }
  78. }
  79. public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1, $handleOnlyReportedErrors = true)
  80. {
  81. $prev = set_error_handler(array($this, 'handleError'), $errorTypes);
  82. $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap);
  83. if ($callPrevious) {
  84. $this->previousErrorHandler = $prev ?: true;
  85. }
  86. $this->handleOnlyReportedErrors = $handleOnlyReportedErrors;
  87. }
  88. public function registerFatalHandler($level = null, $reservedMemorySize = 20)
  89. {
  90. register_shutdown_function(array($this, 'handleFatalError'));
  91. $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);
  92. $this->fatalLevel = $level;
  93. $this->hasFatalErrorHandler = true;
  94. }
  95. public function registerSignalHandler($signo, $level = LogLevel::CRITICAL, $callPrevious = true, $restartSyscalls = true, $async = true)
  96. {
  97. if (!extension_loaded('pcntl') || !function_exists('pcntl_signal')) {
  98. return $this;
  99. }
  100. if ($callPrevious) {
  101. if (function_exists('pcntl_signal_get_handler')) {
  102. $handler = pcntl_signal_get_handler($signo);
  103. if ($handler === false) {
  104. return $this;
  105. }
  106. $this->previousSignalHandler[$signo] = $handler;
  107. } else {
  108. $this->previousSignalHandler[$signo] = true;
  109. }
  110. } else {
  111. unset($this->previousSignalHandler[$signo]);
  112. }
  113. $this->signalLevelMap[$signo] = $level;
  114. $this->signalRestartSyscalls[$signo] = $restartSyscalls;
  115. if (function_exists('pcntl_async_signals') && $async !== null) {
  116. pcntl_async_signals($async);
  117. }
  118. pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls);
  119. return $this;
  120. }
  121. protected function defaultErrorLevelMap()
  122. {
  123. return array(
  124. E_ERROR => LogLevel::CRITICAL,
  125. E_WARNING => LogLevel::WARNING,
  126. E_PARSE => LogLevel::ALERT,
  127. E_NOTICE => LogLevel::NOTICE,
  128. E_CORE_ERROR => LogLevel::CRITICAL,
  129. E_CORE_WARNING => LogLevel::WARNING,
  130. E_COMPILE_ERROR => LogLevel::ALERT,
  131. E_COMPILE_WARNING => LogLevel::WARNING,
  132. E_USER_ERROR => LogLevel::ERROR,
  133. E_USER_WARNING => LogLevel::WARNING,
  134. E_USER_NOTICE => LogLevel::NOTICE,
  135. E_STRICT => LogLevel::NOTICE,
  136. E_RECOVERABLE_ERROR => LogLevel::ERROR,
  137. E_DEPRECATED => LogLevel::NOTICE,
  138. E_USER_DEPRECATED => LogLevel::NOTICE,
  139. );
  140. }
  141. /**
  142. * @private
  143. */
  144. public function handleException($e)
  145. {
  146. $this->logger->log(
  147. $this->uncaughtExceptionLevel === null ? LogLevel::ERROR : $this->uncaughtExceptionLevel,
  148. sprintf('Uncaught Exception %s: "%s" at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()),
  149. array('exception' => $e)
  150. );
  151. if ($this->previousExceptionHandler) {
  152. call_user_func($this->previousExceptionHandler, $e);
  153. }
  154. exit(255);
  155. }
  156. /**
  157. * @private
  158. */
  159. public function handleError($code, $message, $file = '', $line = 0, $context = array())
  160. {
  161. if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) {
  162. return;
  163. }
  164. // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries
  165. if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) {
  166. $level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL;
  167. $this->logger->log($level, self::codeToString($code).': '.$message, array('code' => $code, 'message' => $message, 'file' => $file, 'line' => $line));
  168. } else {
  169. // http://php.net/manual/en/function.debug-backtrace.php
  170. // As of 5.3.6, DEBUG_BACKTRACE_IGNORE_ARGS option was added.
  171. // Any version less than 5.3.6 must use the DEBUG_BACKTRACE_IGNORE_ARGS constant value '2'.
  172. $trace = debug_backtrace((PHP_VERSION_ID < 50306) ? 2 : DEBUG_BACKTRACE_IGNORE_ARGS);
  173. array_shift($trace); // Exclude handleError from trace
  174. $this->lastFatalTrace = $trace;
  175. }
  176. if ($this->previousErrorHandler === true) {
  177. return false;
  178. } elseif ($this->previousErrorHandler) {
  179. return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context);
  180. }
  181. }
  182. /**
  183. * @private
  184. */
  185. public function handleFatalError()
  186. {
  187. $this->reservedMemory = null;
  188. $lastError = error_get_last();
  189. if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) {
  190. $this->logger->log(
  191. $this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel,
  192. 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'],
  193. array('code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $this->lastFatalTrace)
  194. );
  195. if ($this->logger instanceof Logger) {
  196. foreach ($this->logger->getHandlers() as $handler) {
  197. if ($handler instanceof AbstractHandler) {
  198. $handler->close();
  199. }
  200. }
  201. }
  202. }
  203. }
  204. public function handleSignal($signo, array $siginfo = null)
  205. {
  206. static $signals = array();
  207. if (!$signals && extension_loaded('pcntl')) {
  208. $pcntl = new ReflectionExtension('pcntl');
  209. $constants = $pcntl->getConstants();
  210. if (!$constants) {
  211. // HHVM 3.24.2 returns an empty array.
  212. $constants = get_defined_constants(true);
  213. $constants = $constants['Core'];
  214. }
  215. foreach ($constants as $name => $value) {
  216. if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && is_int($value)) {
  217. $signals[$value] = $name;
  218. }
  219. }
  220. unset($constants);
  221. }
  222. $level = isset($this->signalLevelMap[$signo]) ? $this->signalLevelMap[$signo] : LogLevel::CRITICAL;
  223. $signal = isset($signals[$signo]) ? $signals[$signo] : $signo;
  224. $context = isset($siginfo) ? $siginfo : array();
  225. $this->logger->log($level, sprintf('Program received signal %s', $signal), $context);
  226. if (!isset($this->previousSignalHandler[$signo])) {
  227. return;
  228. }
  229. if ($this->previousSignalHandler[$signo] === true || $this->previousSignalHandler[$signo] === SIG_DFL) {
  230. if (extension_loaded('pcntl') && function_exists('pcntl_signal') && function_exists('pcntl_sigprocmask') && function_exists('pcntl_signal_dispatch')
  231. && extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill')) {
  232. $restartSyscalls = isset($this->restartSyscalls[$signo]) ? $this->restartSyscalls[$signo] : true;
  233. pcntl_signal($signo, SIG_DFL, $restartSyscalls);
  234. pcntl_sigprocmask(SIG_UNBLOCK, array($signo), $oldset);
  235. posix_kill(posix_getpid(), $signo);
  236. pcntl_signal_dispatch();
  237. pcntl_sigprocmask(SIG_SETMASK, $oldset);
  238. pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls);
  239. }
  240. } elseif (is_callable($this->previousSignalHandler[$signo])) {
  241. if (PHP_VERSION >= 71000) {
  242. $this->previousSignalHandler[$signo]($signo, $siginfo);
  243. } else {
  244. $this->previousSignalHandler[$signo]($signo);
  245. }
  246. }
  247. }
  248. private static function codeToString($code)
  249. {
  250. switch ($code) {
  251. case E_ERROR:
  252. return 'E_ERROR';
  253. case E_WARNING:
  254. return 'E_WARNING';
  255. case E_PARSE:
  256. return 'E_PARSE';
  257. case E_NOTICE:
  258. return 'E_NOTICE';
  259. case E_CORE_ERROR:
  260. return 'E_CORE_ERROR';
  261. case E_CORE_WARNING:
  262. return 'E_CORE_WARNING';
  263. case E_COMPILE_ERROR:
  264. return 'E_COMPILE_ERROR';
  265. case E_COMPILE_WARNING:
  266. return 'E_COMPILE_WARNING';
  267. case E_USER_ERROR:
  268. return 'E_USER_ERROR';
  269. case E_USER_WARNING:
  270. return 'E_USER_WARNING';
  271. case E_USER_NOTICE:
  272. return 'E_USER_NOTICE';
  273. case E_STRICT:
  274. return 'E_STRICT';
  275. case E_RECOVERABLE_ERROR:
  276. return 'E_RECOVERABLE_ERROR';
  277. case E_DEPRECATED:
  278. return 'E_DEPRECATED';
  279. case E_USER_DEPRECATED:
  280. return 'E_USER_DEPRECATED';
  281. }
  282. return 'Unknown PHP error';
  283. }
  284. }