summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ErrorHandler.php352
-rw-r--r--src/ErrorHandler/ErrorCodes.php84
-rw-r--r--src/ErrorHandler/Logging.php104
-rw-r--r--src/ErrorHandler/Middleware.php76
4 files changed, 402 insertions, 214 deletions
diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php
index 0737322..34c1f67 100644
--- a/src/ErrorHandler.php
+++ b/src/ErrorHandler.php
@@ -2,23 +2,19 @@
namespace Jasny;
-use Psr\Http\Message\ServerRequestInterface;
-use Psr\Http\Message\ResponseInterface;
-use Psr\Log\LoggerInterface;
+use Jasny\ErrorHandler\ErrorCodes;
+use Jasny\ErrorHandler\Logging;
+use Jasny\ErrorHandler\Middleware;
use Psr\Log\LoggerAwareInterface;
-use Psr\Log\LogLevel;
-use Psr\Log\NullLogger;
/**
* Handle error in following middlewares/app actions
*/
class ErrorHandler implements LoggerAwareInterface
{
- /**
- * @var LoggerInterface
- */
- protected $logger;
-
+ use Logging;
+ use ErrorCodes;
+
/**
* @var \Exception|\Error
*/
@@ -30,6 +26,11 @@ class ErrorHandler implements LoggerAwareInterface
protected $chainedErrorHandler;
/**
+ * @var callable|false
+ */
+ protected $chainedExceptionHandler;
+
+ /**
* @var boolean
*/
protected $registeredShutdown = false;
@@ -47,6 +48,12 @@ class ErrorHandler implements LoggerAwareInterface
protected $logErrorTypes = 0;
/**
+ * Log the following error types (in addition to caugth errors)
+ * @var int
+ */
+ protected $logExceptions = [];
+
+ /**
* A string which reserves memory that can be used to log the error in case of an out of memory fatal error
* @var string
*/
@@ -59,98 +66,40 @@ class ErrorHandler implements LoggerAwareInterface
/**
- * Set the logger for logging errors
- *
- * @param LoggerInterface $logger
- */
- public function setLogger(LoggerInterface $logger)
- {
- $this->logger = $logger;
- }
-
- /**
- * Set the logger for logging errors
+ * Set the caught error
*
- * @return LoggerInterface
+ * @param \Throwable|\Exception|\Error
*/
- public function getLogger()
+ public function setError($error)
{
- if (!isset($this->logger)) {
- $this->logger = new NullLogger();
- }
-
- return $this->logger;
- }
-
- /**
- * Log an error or exception
- *
- * @param \Exception|\Error $error
- */
- public function log($error)
- {
- if ($error instanceof \Error || $error instanceof \ErrorException) {
- return $this->logError($error);
- }
-
- if ($error instanceof \Exception) {
- return $this->logException($error);
+ if (!$error instanceof \Error && !$error instanceof \Exception) {
+ $type = (is_object($error) ? get_class($error) . ' ' : '') . gettype($error);
+ trigger_error("Excpeted an Error or Exception, got a $type", E_USER_WARNING);
+ return;
}
- $message = "Unable to log a " . (is_object($error) ? get_class($error) . ' ' : '') . gettype($error);
- $this->getLogger()->log(LogLevel::WARNING, $message);
- }
-
- /**
- * Log an error
- *
- * @param \Error|\ErrorException $error
- */
- protected function logError($error)
- {
- $code = $error instanceof \ErrorException ? $error->getSeverity() : E_ERROR;
- $level = $this->getLogLevel($code);
-
- $message = sprintf('%s: %s at %s line %s', $this->codeToString($code), $error->getMessage(),
- $error->getFile(), $error->getLine());
-
- $context = [
- 'error' => $error,
- 'code' => $code,
- 'message' => $error->getMessage(),
- 'file' => $error->getFile(),
- 'line' => $error->getLine()
- ];
-
- $this->getLogger()->log($level, $message, $context);
+ $this->error = $error;
}
/**
- * Log an exception
+ * Get the caught error
*
- * @param \Exception $error
+ * @return \Throwable|\Exception|\Error
*/
- protected function logException(\Exception $error)
+ public function getError()
{
- $level = $this->getLogLevel();
-
- $message = sprintf('Uncaught Exception %s: "%s" at %s line %s', get_class($error), $error->getMessage(),
- $error->getFile(), $error->getLine());
-
- $context = ['exception' => $error];
-
- $this->getLogger()->log($level, $message, $context);
+ return $this->error;
}
/**
- * Get the caught error
+ * Get the error handler that has been replaced.
*
- * @return \Throwable|\Exception|\Error
+ * @return callable|false|null
*/
- public function getError()
+ public function getChainedErrorHandler()
{
- return $this->error;
+ return $this->chainedErrorHandler;
}
/**
@@ -158,11 +107,12 @@ class ErrorHandler implements LoggerAwareInterface
*
* @return callable|false|null
*/
- public function getChainedErrorHandler()
+ public function getChainedExceptionHandler()
{
- return $this->chainedErrorHandler;
+ return $this->chainedExceptionHandler;
}
+
/**
* Get the types of errors that will be logged
*
@@ -173,83 +123,65 @@ class ErrorHandler implements LoggerAwareInterface
return $this->logErrorTypes;
}
-
/**
- * Run middleware action
- *
- * @param ServerRequestInterface $request
- * @param ResponseInterface $response
- * @param callback $next
- * @return ResponseInterface
+ * Use the global error handler to convert E_USER_ERROR and E_RECOVERABLE_ERROR to an ErrorException
*/
- public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
+ public function converErrorsToExceptions()
{
- if (!is_callable($next)) {
- throw new \InvalidArgumentException("'next' should be a callback");
- }
-
- try {
- $this->error = null;
- $nextResponse = $next($request, $response);
- } catch (\Error $e) {
- $this->error = $e;
- } catch (\Exception $e) {
- $this->error = $e;
- }
-
- if ($this->error) {
- $this->log($this->error);
- $nextResponse = $this->errorResponse($request, $response);
- }
-
- return $nextResponse;
+ $this->convertFatalErrors = true;
+ $this->initErrorHandler();
}
-
+
/**
- * Handle caught error
- *
- * @param ServerRequestInterface $request
- * @param ResponseInterface $response
- * @return ResponseInterface
+ * Log these types of errors or exceptions
+ *
+ * @param int|string $type E_* contants as binary set OR Exception class name
*/
- protected function errorResponse(ServerRequestInterface $request, ResponseInterface $response)
+ public function logUncaught($type)
{
- $errorResponse = $response->withStatus(500);
- $errorResponse->getBody()->write('Unexpected error');
-
- return $errorResponse;
+ if (is_int($type)) {
+ $this->logUncaughtErrors($type);
+ } elseif (is_string($type)) {
+ $this->logUncaughtException($type);
+ } else {
+ throw new \InvalidArgumentException("Type should be an error code (int) or Exception class (string)");
+ }
}
-
/**
- * Use the global error handler to convert E_USER_ERROR and E_RECOVERABLE_ERROR to an ErrorException
+ * Log these types of errors or exceptions
+ *
+ * @param string $class Exception class name
*/
- public function converErrorsToExceptions()
+ protected function logUncaughtException($class)
{
- $this->convertFatalErrors = true;
- $this->initErrorHandler();
+ if (!in_array($class, $this->logExceptions)) {
+ $this->logExceptions[] = $class;
+ }
+
+ $this->initExceptionHandler();
}
/**
- * Also log these types of errors in addition to caught errors and exceptions
+ * Log these types of errors or exceptions
*
- * @param int $errorTypes E_* contants as binary set
+ * @param int $type E_* contants as binary set
*/
- public function alsoLog($errorTypes)
+ protected function logUncaughtErrors($type)
{
- $this->logErrorTypes |= $errorTypes;
-
- $nonFatal = E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_STRICT | E_DEPRECATED | E_USER_DEPRECATED;
+ $this->logErrorTypes |= $type;
+
$unhandled = E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR;
-
- if ($this->logErrorTypes & $nonFatal) {
+
+ if ($type & ~$unhandled) {
$this->initErrorHandler();
}
-
- if ($this->logErrorTypes & $unhandled) {
+
+ if ($type & $unhandled) {
$this->initShutdownFunction();
}
}
+
/**
* Set a callback for when the script dies because of a fatal, non-catchable error.
@@ -271,6 +203,15 @@ class ErrorHandler implements LoggerAwareInterface
}
/**
+ * Use this error handler as middleware
+ */
+ public function asMiddleware()
+ {
+ return new Middleware($this);
+ }
+
+
+ /**
* Use the global error handler
*/
protected function initErrorHandler()
@@ -309,6 +250,51 @@ class ErrorHandler implements LoggerAwareInterface
: false;
}
+
+ /**
+ * Use the global error handler
+ */
+ protected function initExceptionHandler()
+ {
+ if (!isset($this->chainedExceptionHandler)) {
+ $this->chainedExceptionHandler = $this->setExceptionHandler([$this, 'handleException']) ?: false;
+ }
+ }
+
+ /**
+ * Uncaught error handler
+ * @ignore
+ *
+ * @param \Exception|\Error $exception
+ */
+ public function handleException($exception)
+ {
+ $isInstanceOf = array_map(function($class) use ($exception) {
+ return is_a($exception, $class);
+ }, $this->logExceptions);
+
+ $shouldLog = array_sum($isInstanceOf) > 0;
+
+ if ($shouldLog) {
+ $this->log($exception);
+ }
+
+ if ($this->chainedExceptionHandler) {
+ call_user_func($this->chainedErrorHandler, $type, $message, $file, $line, $context);
+ }
+
+ set_error_handler(null);
+
+ $warning = sprintf("Uncaught exception '%s' with message '%s' in %s:%d", get_class($exception),
+ $exception->getMessage(), $exception->getFile(), $exception->getLine());
+ trigger_error($warning, E_USER_WARNING);
+
+ if ($this->onFatalError) {
+ call_user_func($this->onFatalError, $exception);
+ }
+ }
+
+
/**
* Reserve memory for shutdown function in case of out of memory
*/
@@ -356,81 +342,7 @@ class ErrorHandler implements LoggerAwareInterface
}
}
-
- /**
- * Get the log level for an error code
- *
- * @param int $code E_* error code
- * @return string
- */
- protected function getLogLevel($code = null)
- {
- switch ($code) {
- case E_STRICT:
- case E_DEPRECATED:
- case E_USER_DEPRECATED:
- return LogLevel::INFO;
-
- case E_NOTICE:
- case E_USER_NOTICE:
- return LogLevel::NOTICE;
-
- case E_WARNING:
- case E_CORE_WARNING:
- case E_COMPILE_WARNING:
- case E_USER_WARNING:
- return LogLevel::WARNING;
-
- case E_PARSE:
- case E_CORE_ERROR:
- case E_COMPILE_ERROR:
- return LogLevel::CRITICAL;
-
- default:
- return LogLevel::ERROR;
- }
- }
-
- /**
- * Turn an error code into a string
- *
- * @param int $code
- * @return string
- */
- protected function codeToString($code)
- {
- switch ($code) {
- case E_ERROR:
- case E_USER_ERROR:
- case E_RECOVERABLE_ERROR:
- return 'Fatal error';
- case E_WARNING:
- case E_USER_WARNING:
- return 'Warning';
- case E_PARSE:
- return 'Parse error';
- case E_NOTICE:
- case E_USER_NOTICE:
- return 'Notice';
- case E_CORE_ERROR:
- return 'Core error';
- case E_CORE_WARNING:
- return 'Core warning';
- case E_COMPILE_ERROR:
- return 'Compile error';
- case E_COMPILE_WARNING:
- return 'Compile warning';
- case E_STRICT:
- return 'Strict standards';
- case E_DEPRECATED:
- case E_USER_DEPRECATED:
- return 'Deprecated';
- }
-
- return 'Unknown error';
- }
-
-
+
/**
* Clear and destroy all the output buffers
* @codeCoverageIgnore
@@ -478,6 +390,18 @@ class ErrorHandler implements LoggerAwareInterface
}
/**
+ * Wrapper method for `set_exception_handler`
+ * @codeCoverageIgnore
+ *
+ * @param callable $callback
+ * @return callable|null
+ */
+ protected function setExceptionHandler($callback)
+ {
+ return set_error_handler($callback);
+ }
+
+ /**
* Wrapper method for `register_shutdown_function`
* @codeCoverageIgnore
*
diff --git a/src/ErrorHandler/ErrorCodes.php b/src/ErrorHandler/ErrorCodes.php
new file mode 100644
index 0000000..6ef2957
--- /dev/null
+++ b/src/ErrorHandler/ErrorCodes.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Jasny\ErrorHandler;
+
+use Psr\Log\LogLevel;
+
+/**
+ * Trait for using E_* error codes
+ */
+trait ErrorCodes
+{
+ /**
+ * Get the log level for an error code
+ *
+ * @param int $code E_* error code
+ * @return string
+ */
+ protected function getLogLevel($code = null)
+ {
+ switch ($code) {
+ case E_STRICT:
+ case E_DEPRECATED:
+ case E_USER_DEPRECATED:
+ return LogLevel::INFO;
+
+ case E_NOTICE:
+ case E_USER_NOTICE:
+ return LogLevel::NOTICE;
+
+ case E_WARNING:
+ case E_CORE_WARNING:
+ case E_COMPILE_WARNING:
+ case E_USER_WARNING:
+ return LogLevel::WARNING;
+
+ case E_PARSE:
+ case E_CORE_ERROR:
+ case E_COMPILE_ERROR:
+ return LogLevel::CRITICAL;
+
+ default:
+ return LogLevel::ERROR;
+ }
+ }
+
+ /**
+ * Turn an error code into a string
+ *
+ * @param int $code
+ * @return string
+ */
+ protected function codeToString($code)
+ {
+ switch ($code) {
+ case E_ERROR:
+ case E_USER_ERROR:
+ case E_RECOVERABLE_ERROR:
+ return 'Fatal error';
+ case E_WARNING:
+ case E_USER_WARNING:
+ return 'Warning';
+ case E_PARSE:
+ return 'Parse error';
+ case E_NOTICE:
+ case E_USER_NOTICE:
+ return 'Notice';
+ case E_CORE_ERROR:
+ return 'Core error';
+ case E_CORE_WARNING:
+ return 'Core warning';
+ case E_COMPILE_ERROR:
+ return 'Compile error';
+ case E_COMPILE_WARNING:
+ return 'Compile warning';
+ case E_STRICT:
+ return 'Strict standards';
+ case E_DEPRECATED:
+ case E_USER_DEPRECATED:
+ return 'Deprecated';
+ }
+
+ return 'Unknown error';
+ }
+} \ No newline at end of file
diff --git a/src/ErrorHandler/Logging.php b/src/ErrorHandler/Logging.php
new file mode 100644
index 0000000..6d531bd
--- /dev/null
+++ b/src/ErrorHandler/Logging.php
@@ -0,0 +1,104 @@
+<?php
+
+namespace Jasny\ErrorHandler;
+
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
+use Psr\Log\NullLogger;
+
+/**
+ * Trait for logging errors and exceptions
+ */
+trait Logging
+{
+ /**
+ * @var LoggerInterface
+ */
+ protected $logger;
+
+
+ /**
+ * Set the logger for logging errors
+ *
+ * @param LoggerInterface $logger
+ */
+ public function setLogger(LoggerInterface $logger)
+ {
+ $this->logger = $logger;
+ }
+
+ /**
+ * Set the logger for logging errors
+ *
+ * @return LoggerInterface
+ */
+ public function getLogger()
+ {
+ if (!isset($this->logger)) {
+ $this->logger = new NullLogger();
+ }
+
+ return $this->logger;
+ }
+
+
+ /**
+ * Log an error or exception
+ *
+ * @param \Exception|\Error $error
+ */
+ public function log($error)
+ {
+ if ($error instanceof \Error || $error instanceof \ErrorException) {
+ return $this->logError($error);
+ }
+
+ if ($error instanceof \Exception) {
+ return $this->logException($error);
+ }
+
+ $message = "Unable to log a " . (is_object($error) ? get_class($error) . ' ' : '') . gettype($error);
+ $this->getLogger()->log(LogLevel::WARNING, $message);
+ }
+
+ /**
+ * Log an error
+ *
+ * @param \Error|\ErrorException $error
+ */
+ protected function logError($error)
+ {
+ $code = $error instanceof \ErrorException ? $error->getSeverity() : E_ERROR;
+ $level = $this->getLogLevel($code);
+
+ $message = sprintf('%s: %s at %s line %s', $this->codeToString($code), $error->getMessage(),
+ $error->getFile(), $error->getLine());
+
+ $context = [
+ 'error' => $error,
+ 'code' => $code,
+ 'message' => $error->getMessage(),
+ 'file' => $error->getFile(),
+ 'line' => $error->getLine()
+ ];
+
+ $this->getLogger()->log($level, $message, $context);
+ }
+
+ /**
+ * Log an exception
+ *
+ * @param \Exception $exception
+ */
+ protected function logException(\Exception $exception)
+ {
+ $level = $this->getLogLevel();
+
+ $message = sprintf('Uncaught Exception %s: "%s" at %s line %s', get_class($exception), $exception->getMessage(),
+ $exception->getFile(), $exception->getLine());
+
+ $context = compact('exception');
+
+ $this->getLogger()->log($level, $message, $context);
+ }
+}
diff --git a/src/ErrorHandler/Middleware.php b/src/ErrorHandler/Middleware.php
new file mode 100644
index 0000000..710f6e7
--- /dev/null
+++ b/src/ErrorHandler/Middleware.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Jasny\ErrorHandler;
+
+use Jasny\ErrorHandler;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Use error handler as middleware
+ */
+class Middleware
+{
+ /**
+ * @var ErrorHandler
+ */
+ protected $errorHandler;
+
+ /**
+ * Class constructor
+ *
+ * @param ErrorHandler $errorHandler
+ */
+ public function __construct(ErrorHandler $errorHandler)
+ {
+ $this->errorHandler = $errorHandler;
+ }
+
+ /**
+ * Run middleware action
+ *
+ * @param ServerRequestInterface $request
+ * @param ResponseInterface $response
+ * @param callback $next
+ * @return ResponseInterface
+ */
+ public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
+ {
+ if (!is_callable($next)) {
+ throw new \InvalidArgumentException("'next' should be a callback");
+ }
+
+ try {
+ $nextResponse = $next($request, $response);
+ $error = null;
+ } catch (\Error $e) {
+ $error = $e;
+ } catch (\Exception $e) {
+ $error = $e;
+ }
+
+ $this->errorHandler->setError($error);
+
+ if ($error) {
+ $this->errorHandler->log($error);
+ $nextResponse = $this->errorResponse($request, $response);
+ }
+
+ return $nextResponse;
+ }
+
+ /**
+ * Handle caught error
+ *
+ * @param ServerRequestInterface $request
+ * @param ResponseInterface $response
+ * @return ResponseInterface
+ */
+ protected function errorResponse(ServerRequestInterface $request, ResponseInterface $response)
+ {
+ $errorResponse = $response->withStatus(500);
+ $errorResponse->getBody()->write('Unexpected error');
+
+ return $errorResponse;
+ }
+}