BaseErrorHandler.php 7.7 KB

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