BaseErrorHandler.php 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 2.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Error;
  16. use Cake\Core\Configure;
  17. use Cake\Error\Debugger;
  18. use Cake\Log\Log;
  19. use Cake\Routing\Router;
  20. /**
  21. * Base error handler that provides logic common to the CLI + web
  22. * error/exception handlers.
  23. *
  24. * Subclasses are required to implement the template methods to handle displaying
  25. * the errors in their environment.
  26. */
  27. abstract class BaseErrorHandler
  28. {
  29. /**
  30. * Display an error message in an environment specific way.
  31. *
  32. * Subclasses should implement this method to display the error as
  33. * desired for the runtime they operate in.
  34. *
  35. * @param array $error An array of error data.
  36. * @param bool $debug Whether or not the app is in debug mode.
  37. * @return void
  38. */
  39. abstract protected function _displayError($error, $debug);
  40. /**
  41. * Display an exception in an environment specific way.
  42. *
  43. * Subclasses should implement this method to display an uncaught exception as
  44. * desired for the runtime they operate in.
  45. *
  46. * @param \Exception $exception The uncaught exception.
  47. * @return void
  48. */
  49. abstract protected function _displayException($exception);
  50. /**
  51. * Register the error and exception handlers.
  52. *
  53. * @return void
  54. */
  55. public function register()
  56. {
  57. $level = -1;
  58. if (isset($this->_options['errorLevel'])) {
  59. $level = $this->_options['errorLevel'];
  60. }
  61. error_reporting($level);
  62. set_error_handler([$this, 'handleError'], $level);
  63. set_exception_handler([$this, 'handleException']);
  64. register_shutdown_function(function () {
  65. if (PHP_SAPI === 'cli') {
  66. return;
  67. }
  68. $error = error_get_last();
  69. if (!is_array($error)) {
  70. return;
  71. }
  72. $fatals = [
  73. E_USER_ERROR,
  74. E_ERROR,
  75. E_PARSE,
  76. ];
  77. if (!in_array($error['type'], $fatals, true)) {
  78. return;
  79. }
  80. $this->handleFatalError(
  81. $error['type'],
  82. $error['message'],
  83. $error['file'],
  84. $error['line']
  85. );
  86. });
  87. }
  88. /**
  89. * Set as the default error handler by CakePHP.
  90. *
  91. * Use config/error.php to customize or replace this error handler.
  92. * This function will use Debugger to display errors when debug > 0. And
  93. * will log errors to Log, when debug == 0.
  94. *
  95. * You can use the 'errorLevel' option to set what type of errors will be handled.
  96. * Stack traces for errors can be enabled with the 'trace' option.
  97. *
  98. * @param int $code Code of error
  99. * @param string $description Error description
  100. * @param string|null $file File on which error occurred
  101. * @param int|null $line Line that triggered the error
  102. * @param array|null $context Context
  103. * @return bool True if error was handled
  104. */
  105. public function handleError($code, $description, $file = null, $line = null, $context = null)
  106. {
  107. if (error_reporting() === 0) {
  108. return false;
  109. }
  110. list($error, $log) = $this->mapErrorCode($code);
  111. if ($log === LOG_ERR) {
  112. return $this->handleFatalError($code, $description, $file, $line);
  113. }
  114. $data = [
  115. 'level' => $log,
  116. 'code' => $code,
  117. 'error' => $error,
  118. 'description' => $description,
  119. 'file' => $file,
  120. 'line' => $line,
  121. ];
  122. $debug = Configure::read('debug');
  123. if ($debug) {
  124. $data += [
  125. 'context' => $context,
  126. 'start' => 3,
  127. 'path' => Debugger::trimPath($file)
  128. ];
  129. }
  130. $this->_displayError($data, $debug);
  131. $this->_logError($log, $data);
  132. return true;
  133. }
  134. /**
  135. * Handle uncaught exceptions.
  136. *
  137. * Uses a template method provided by subclasses to display errors in an
  138. * environment appropriate way.
  139. *
  140. * @param \Exception $exception Exception instance.
  141. * @return void
  142. * @throws \Exception When renderer class not found
  143. * @see http://php.net/manual/en/function.set-exception-handler.php
  144. */
  145. public function handleException(\Exception $exception)
  146. {
  147. $this->_displayException($exception);
  148. $this->_logException($exception);
  149. $this->_stop($exception->getCode() ?: 1);
  150. }
  151. /**
  152. * Stop the process.
  153. *
  154. * Implemented in subclasses that need it.
  155. *
  156. * @param int $code Exit code.
  157. * @return void
  158. */
  159. protected function _stop($code)
  160. {
  161. // Do nothing.
  162. }
  163. /**
  164. * Display/Log a fatal error.
  165. *
  166. * @param int $code Code of error
  167. * @param string $description Error description
  168. * @param string $file File on which error occurred
  169. * @param int $line Line that triggered the error
  170. * @return bool
  171. */
  172. public function handleFatalError($code, $description, $file, $line)
  173. {
  174. $data = [
  175. 'code' => $code,
  176. 'description' => $description,
  177. 'file' => $file,
  178. 'line' => $line,
  179. 'error' => 'Fatal Error',
  180. ];
  181. $this->_logError(LOG_ERR, $data);
  182. $this->handleException(new FatalErrorException($description, 500, $file, $line));
  183. return true;
  184. }
  185. /**
  186. * Log an error.
  187. *
  188. * @param string $level The level name of the log.
  189. * @param array $data Array of error data.
  190. * @return bool
  191. */
  192. protected function _logError($level, $data)
  193. {
  194. $message = sprintf(
  195. '%s (%s): %s in [%s, line %s]',
  196. $data['error'],
  197. $data['code'],
  198. $data['description'],
  199. $data['file'],
  200. $data['line']
  201. );
  202. if (!empty($this->_options['trace'])) {
  203. $trace = Debugger::trace([
  204. 'start' => 1,
  205. 'format' => 'log'
  206. ]);
  207. $message .= "\nTrace:\n" . $trace . "\n";
  208. }
  209. $message .= "\n\n";
  210. return Log::write($level, $message);
  211. }
  212. /**
  213. * Handles exception logging
  214. *
  215. * @param \Exception $exception Exception instance.
  216. * @return bool
  217. */
  218. protected function _logException(\Exception $exception)
  219. {
  220. $config = $this->_options;
  221. if (empty($config['log'])) {
  222. return false;
  223. }
  224. if (!empty($config['skipLog'])) {
  225. foreach ((array)$config['skipLog'] as $class) {
  226. if ($exception instanceof $class) {
  227. return false;
  228. }
  229. }
  230. }
  231. return Log::error($this->_getMessage($exception));
  232. }
  233. /**
  234. * Generates a formatted error message
  235. *
  236. * @param \Exception $exception Exception instance
  237. * @return string Formatted message
  238. */
  239. protected function _getMessage(\Exception $exception)
  240. {
  241. $message = sprintf(
  242. "[%s] %s",
  243. get_class($exception),
  244. $exception->getMessage()
  245. );
  246. if (method_exists($exception, 'getAttributes')) {
  247. $attributes = $exception->getAttributes();
  248. if ($attributes) {
  249. $message .= "\nException Attributes: " . var_export($exception->getAttributes(), true);
  250. }
  251. }
  252. if (PHP_SAPI !== 'cli') {
  253. $request = Router::getRequest();
  254. if ($request) {
  255. $message .= "\nRequest URL: " . $request->here();
  256. }
  257. }
  258. $message .= "\nStack Trace:\n" . $exception->getTraceAsString() . "\n\n";
  259. return $message;
  260. }
  261. /**
  262. * Map an error code into an Error word, and log location.
  263. *
  264. * @param int $code Error code to map
  265. * @return array Array of error word, and log location.
  266. */
  267. public static function mapErrorCode($code)
  268. {
  269. $levelMap = [
  270. E_PARSE => 'error',
  271. E_ERROR => 'error',
  272. E_CORE_ERROR => 'error',
  273. E_COMPILE_ERROR => 'error',
  274. E_USER_ERROR => 'error',
  275. E_WARNING => 'warning',
  276. E_USER_WARNING => 'warning',
  277. E_COMPILE_WARNING => 'warning',
  278. E_RECOVERABLE_ERROR => 'warning',
  279. E_NOTICE => 'notice',
  280. E_USER_NOTICE => 'notice',
  281. E_STRICT => 'strict',
  282. E_DEPRECATED => 'deprecated',
  283. E_USER_DEPRECATED => 'deprecated',
  284. ];
  285. $logMap = [
  286. 'error' => LOG_ERR,
  287. 'warning' => LOG_WARNING,
  288. 'notice' => LOG_NOTICE,
  289. 'strict' => LOG_NOTICE,
  290. 'deprecated' => LOG_NOTICE,
  291. ];
  292. $error = $levelMap[$code];
  293. $log = $logMap[$error];
  294. return [ucfirst($error), $log];
  295. }
  296. }