BaseErrorHandler.php 7.7 KB

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