ErrorHandler.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. <?php
  2. /*
  3. * This file is part of Raven.
  4. *
  5. * (c) Sentry Team
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. /**
  11. * Event handlers for exceptions and errors
  12. *
  13. * $client = new Raven_Client('http://public:secret/example.com/1');
  14. * $error_handler = new Raven_ErrorHandler($client);
  15. * $error_handler->registerExceptionHandler();
  16. * $error_handler->registerErrorHandler();
  17. * $error_handler->registerShutdownFunction();
  18. *
  19. * @package raven
  20. */
  21. // TODO(dcramer): deprecate default error types in favor of runtime configuration
  22. // unless a reason can be determined that making them dynamic is better. They
  23. // currently are not used outside of the fatal handler.
  24. class Raven_ErrorHandler
  25. {
  26. protected $old_exception_handler;
  27. protected $call_existing_exception_handler = false;
  28. protected $old_error_handler;
  29. protected $call_existing_error_handler = false;
  30. protected $reservedMemory;
  31. /** @var Raven_Client */
  32. protected $client;
  33. protected $send_errors_last = false;
  34. protected $fatal_error_types = array(
  35. E_ERROR,
  36. E_PARSE,
  37. E_CORE_ERROR,
  38. E_CORE_WARNING,
  39. E_COMPILE_ERROR,
  40. E_COMPILE_WARNING,
  41. E_STRICT,
  42. );
  43. /**
  44. * @var array
  45. * Error types which should be processed by the handler.
  46. * A 'null' value implies "whatever error_reporting is at time of error".
  47. */
  48. protected $error_types = null;
  49. /** @var \Exception|null */
  50. private $lastHandledException;
  51. public function __construct($client, $send_errors_last = false, $error_types = null,
  52. $__error_types = null)
  53. {
  54. // support legacy fourth argument for error types
  55. if ($error_types === null) {
  56. $error_types = $__error_types;
  57. }
  58. $this->client = $client;
  59. $this->error_types = $error_types;
  60. $this->fatal_error_types = array_reduce($this->fatal_error_types, array($this, 'bitwiseOr'));
  61. if ($send_errors_last) {
  62. $this->send_errors_last = true;
  63. $this->client->store_errors_for_bulk_send = true;
  64. }
  65. }
  66. public function bitwiseOr($a, $b)
  67. {
  68. return $a | $b;
  69. }
  70. public function handleException($e, $isError = false, $vars = null)
  71. {
  72. $e->event_id = $this->client->captureException($e, null, null, $vars);
  73. $this->lastHandledException = $e;
  74. if (!$isError && $this->call_existing_exception_handler) {
  75. if ($this->old_exception_handler !== null) {
  76. call_user_func($this->old_exception_handler, $e);
  77. } else {
  78. throw $e;
  79. }
  80. }
  81. }
  82. public function handleError($type, $message, $file = '', $line = 0, $context = array())
  83. {
  84. // http://php.net/set_error_handler
  85. // The following error types cannot be handled with a user defined function: E_ERROR,
  86. // E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and
  87. // most of E_STRICT raised in the file where set_error_handler() is called.
  88. if (error_reporting() !== 0) {
  89. $error_types = $this->error_types;
  90. if ($error_types === null) {
  91. $error_types = error_reporting();
  92. }
  93. if ($error_types & $type) {
  94. $e = new ErrorException($message, 0, $type, $file, $line);
  95. $this->handleException($e, true, $context);
  96. }
  97. }
  98. if ($this->call_existing_error_handler) {
  99. if ($this->old_error_handler !== null) {
  100. return call_user_func(
  101. $this->old_error_handler,
  102. $type,
  103. $message,
  104. $file,
  105. $line,
  106. $context
  107. );
  108. } else {
  109. return false;
  110. }
  111. }
  112. return true;
  113. }
  114. public function handleFatalError()
  115. {
  116. unset($this->reservedMemory);
  117. if (null === $error = error_get_last()) {
  118. return;
  119. }
  120. if ($this->shouldCaptureFatalError($error['type'], $error['message'])) {
  121. $e = new ErrorException(
  122. @$error['message'], 0, @$error['type'],
  123. @$error['file'], @$error['line']
  124. );
  125. $this->client->useCompression = $this->client->useCompression && PHP_VERSION_ID > 70000;
  126. $this->handleException($e, true);
  127. }
  128. }
  129. /**
  130. * @param int $type
  131. * @param string|null $message
  132. * @return bool
  133. */
  134. public function shouldCaptureFatalError($type, $message = null)
  135. {
  136. if (PHP_VERSION_ID >= 70000 && $this->lastHandledException) {
  137. if ($type === E_CORE_ERROR && strpos($message, 'Exception thrown without a stack frame') === 0) {
  138. return false;
  139. }
  140. if ($type === E_ERROR) {
  141. $expectedMessage = 'Uncaught '
  142. . \get_class($this->lastHandledException)
  143. . ': '
  144. . $this->lastHandledException->getMessage();
  145. if (strpos($message, $expectedMessage) === 0) {
  146. return false;
  147. }
  148. }
  149. }
  150. return (bool) ($type & $this->fatal_error_types);
  151. }
  152. /**
  153. * Register a handler which will intercept unhandled exceptions and report them to the
  154. * associated Sentry client.
  155. *
  156. * @param bool $call_existing Call any existing exception handlers after processing
  157. * this instance.
  158. * @return Raven_ErrorHandler
  159. */
  160. public function registerExceptionHandler($call_existing = true)
  161. {
  162. $this->old_exception_handler = set_exception_handler(array($this, 'handleException'));
  163. $this->call_existing_exception_handler = $call_existing;
  164. return $this;
  165. }
  166. /**
  167. * Register a handler which will intercept standard PHP errors and report them to the
  168. * associated Sentry client.
  169. *
  170. * @param bool $call_existing Call any existing errors handlers after processing
  171. * this instance.
  172. * @param array $error_types All error types that should be sent.
  173. * @return Raven_ErrorHandler
  174. */
  175. public function registerErrorHandler($call_existing = true, $error_types = null)
  176. {
  177. if ($error_types !== null) {
  178. $this->error_types = $error_types;
  179. }
  180. $this->old_error_handler = set_error_handler(array($this, 'handleError'), E_ALL);
  181. $this->call_existing_error_handler = $call_existing;
  182. return $this;
  183. }
  184. /**
  185. * Register a fatal error handler, which will attempt to capture errors which
  186. * shutdown the PHP process. These are commonly things like OOM or timeouts.
  187. *
  188. * @param int $reservedMemorySize Number of kilobytes memory space to reserve,
  189. * which is utilized when handling fatal errors.
  190. * @return Raven_ErrorHandler
  191. */
  192. public function registerShutdownFunction($reservedMemorySize = 10)
  193. {
  194. register_shutdown_function(array($this, 'handleFatalError'));
  195. $this->reservedMemory = str_repeat('x', 1024 * $reservedMemorySize);
  196. return $this;
  197. }
  198. }