ExceptionTrap.php 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. <?php
  2. namespace Tools\Error;
  3. use Cake\Core\Configure;
  4. use Cake\Core\Exception\CakeException;
  5. use Cake\Error\Debugger;
  6. use Cake\Error\ExceptionTrap as CoreExceptionTrap;
  7. use Cake\Http\ServerRequest;
  8. use Cake\Log\Log;
  9. use Psr\Http\Message\ServerRequestInterface;
  10. use Throwable;
  11. /**
  12. * Custom ErrorHandler to not mix the 404 exceptions with the rest of "real" errors in the error.log file.
  13. *
  14. * All you need to do is:
  15. * - switch `use Cake\Error\ExceptionTrap;` with `use Tools\Error\ExceptionTrap;` in your bootstrap
  16. * - Make sure you got the 404 log defined either in your app.php or as Log::config() call.
  17. *
  18. * Example config as scoped one:
  19. * - 'className' => 'Cake\Log\Engine\FileLog',
  20. * - 'path' => LOGS,
  21. * - 'file'=> '404',
  22. * - 'levels' => ['error'],
  23. * - 'scopes' => ['404']
  24. *
  25. * If you don't want the errors to also show up in the debug and error log, make sure you set
  26. * `'scopes' => false` for those two in your app.php file.
  27. *
  28. * In case you need custom 404 mappings for some additional custom exceptions, make use of `log404` option.
  29. * It will overwrite the current defaults completely.
  30. */
  31. class ExceptionTrap extends CoreExceptionTrap {
  32. use ErrorHandlerTrait;
  33. /**
  34. * Constructor
  35. *
  36. * @param array $config The options for error handling.
  37. */
  38. public function __construct(array $config = []) {
  39. $config += (array)Configure::read('Error');
  40. $config += [
  41. 'logger' => ErrorLogger::class,
  42. ];
  43. parent::__construct($config);
  44. }
  45. /**
  46. * Log an error for the exception if applicable.
  47. *
  48. * @param \Exception $exception The exception to log a message for.
  49. * @param \Psr\Http\Message\ServerRequestInterface|null $request The current request.
  50. *
  51. * @return void
  52. */
  53. public function logException(Throwable $exception, ?ServerRequestInterface $request = null): void {
  54. if ($this->is404($exception, $request)) {
  55. $level = LOG_ERR;
  56. $message = $this->getMessage($exception);
  57. if ($request !== null) {
  58. $message .= $this->getRequestContext($request);
  59. }
  60. Log::write($level, $message, ['404']);
  61. return;
  62. }
  63. parent::logException($exception, $request);
  64. }
  65. /**
  66. * Generate the message for the exception
  67. *
  68. * @param \Throwable $exception The exception to log a message for.
  69. * @param bool $isPrevious False for original exception, true for previous
  70. * @param bool $includeTrace Whether to include a stack trace.
  71. * @return string Error message
  72. */
  73. protected function getMessage(Throwable $exception, bool $isPrevious = false, bool $includeTrace = false): string {
  74. $message = sprintf(
  75. '%s[%s] %s in %s on line %s',
  76. $isPrevious ? "\nCaused by: " : '',
  77. $exception::class,
  78. $exception->getMessage(),
  79. $exception->getFile(),
  80. $exception->getLine(),
  81. );
  82. $debug = Configure::read('debug');
  83. if ($debug && $exception instanceof CakeException) {
  84. $attributes = $exception->getAttributes();
  85. if ($attributes) {
  86. $message .= "\nException Attributes: " . var_export($exception->getAttributes(), true);
  87. }
  88. }
  89. if ($includeTrace) {
  90. $trace = Debugger::formatTrace($exception, ['format' => 'points']);
  91. assert(is_array($trace));
  92. $message .= "\nStack Trace:\n";
  93. foreach ($trace as $line) {
  94. if (is_string($line)) {
  95. $message .= '- ' . $line;
  96. } else {
  97. $message .= "- {$line['file']}:{$line['line']}\n";
  98. }
  99. }
  100. }
  101. $previous = $exception->getPrevious();
  102. if ($previous) {
  103. $message .= $this->getMessage($previous, true, $includeTrace);
  104. }
  105. return $message;
  106. }
  107. /**
  108. * Get the request context for an error/exception trace.
  109. *
  110. * @param \Psr\Http\Message\ServerRequestInterface $request The request to read from.
  111. * @return string
  112. */
  113. protected function getRequestContext(ServerRequestInterface $request): string {
  114. $message = "\nRequest URL: " . $request->getRequestTarget();
  115. $referer = $request->getHeaderLine('Referer');
  116. if ($referer) {
  117. $message .= "\nReferer URL: " . $referer;
  118. }
  119. if ($request instanceof ServerRequest) {
  120. $clientIp = $request->clientIp();
  121. if ($clientIp && $clientIp !== '::1') {
  122. $message .= "\nClient IP: " . $clientIp;
  123. }
  124. }
  125. return $message;
  126. }
  127. }