From f15cb63d323b47d76091b16c7b2852ec0cc60747 Mon Sep 17 00:00:00 2001 From: Mike Koch Date: Sun, 12 Mar 2017 20:50:54 -0400 Subject: [PATCH] Logging has been implemented for exceptions --- api/ApplicationContext.php | 4 ++ api/DataAccess/Logging/LoggingGateway.php | 47 ++++++++++++++++ api/DataAccess/Logging/Severity.php | 11 ++++ api/index.php | 65 +++++++++++++++++++++-- install/mods-for-hesk/sql/installSql.php | 7 +++ 5 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 api/DataAccess/Logging/LoggingGateway.php create mode 100644 api/DataAccess/Logging/Severity.php diff --git a/api/ApplicationContext.php b/api/ApplicationContext.php index 4672efc0..5e873c38 100644 --- a/api/ApplicationContext.php +++ b/api/ApplicationContext.php @@ -18,6 +18,7 @@ use BusinessLogic\Tickets\TicketValidators; use BusinessLogic\Tickets\TrackingIdGenerator; use BusinessLogic\Tickets\VerifiedEmailChecker; use DataAccess\Categories\CategoryGateway; +use DataAccess\Logging\LoggingGateway; use DataAccess\Security\BanGateway; use DataAccess\Security\UserGateway; use DataAccess\Settings\ModsForHeskSettingsGateway; @@ -38,6 +39,9 @@ class ApplicationContext { // API Checker $this->get[ApiChecker::class] = new ApiChecker($this->get[ModsForHeskSettingsGateway::class]); + // Logging + $this->get[LoggingGateway::class] = new LoggingGateway(); + // Verified Email Checker $this->get[VerifiedEmailGateway::class] = new VerifiedEmailGateway(); $this->get[VerifiedEmailChecker::class] = new VerifiedEmailChecker($this->get[VerifiedEmailGateway::class]); diff --git a/api/DataAccess/Logging/LoggingGateway.php b/api/DataAccess/Logging/LoggingGateway.php new file mode 100644 index 00000000..0df48939 --- /dev/null +++ b/api/DataAccess/Logging/LoggingGateway.php @@ -0,0 +1,47 @@ +log(Severity::DEBUG, $location, $message, $stackTrace, $userContext, $heskSettings); + } + + function logInfo($location, $message, $stackTrace, $userContext, $heskSettings) { + return $this->log(Severity::INFO, $location, $message, $stackTrace, $userContext, $heskSettings); + } + + function logWarning($location, $message, $stackTrace, $userContext, $heskSettings) { + return $this->log(Severity::WARNING, $location, $message, $stackTrace, $userContext, $heskSettings); + } + + function logError($location, $message, $stackTrace, $userContext, $heskSettings) { + return $this->log(Severity::ERROR, $location, $message, $stackTrace, $userContext, $heskSettings); + } + + /** + * @param $severity int (from Severity) + * @param $location string + * @param $message string + * @param $userContext UserContext + * @param $heskSettings array + * @return int|null|string The inserted ID, or null on failure. + */ + private function log($severity, $location, $message, $stackTrace, $userContext, $heskSettings) { + $this->init(); + + hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "logging` (`username`, `message`, `severity`, `location`, `timestamp`, `stack_trace`) + VALUES ('" . hesk_dbEscape($userContext->username) . "', + '" . hesk_dbEscape($message) . "', " . intval($severity) . ", '" . hesk_dbEscape($location) . "', NOW(), '" . hesk_dbEscape($stackTrace) . "')"); + + $insertedId = hesk_dbInsertID(); + + $this->close(); + + return $insertedId; + } +} \ No newline at end of file diff --git a/api/DataAccess/Logging/Severity.php b/api/DataAccess/Logging/Severity.php new file mode 100644 index 00000000..46124c35 --- /dev/null +++ b/api/DataAccess/Logging/Severity.php @@ -0,0 +1,11 @@ +get[\BusinessLogic\Settings\ApiChecker::class]; if (!$apiChecker->isApiEnabled($hesk_settings)) { - http_response_code(404); + print output(array('message' => 'API Disabled'), 404); die(); } @@ -53,7 +53,18 @@ function errorHandler($errorNumber, $errorMessage, $errorFile, $errorLine) { * @param $exception Exception */ function exceptionHandler($exception) { - //-- TODO Log an error + global $applicationContext, $userContext, $hesk_settings; + + if (strpos($exception->getTraceAsString(), 'LoggingGateway') !== false) { + //-- Suppress these for now, as it would cause issues to output two JSONs at one time. + return; + } + + + /* @var $loggingGateway \DataAccess\Logging\LoggingGateway */ + $loggingGateway = $applicationContext->get[\DataAccess\Logging\LoggingGateway::class]; + + // We don't cast API Friendly Exceptions as they're user-generated errors if (exceptionIsOfType($exception, \BusinessLogic\Exceptions\ApiFriendlyException::class)) { /* @var $castedException \BusinessLogic\Exceptions\ApiFriendlyException */ $castedException = $exception; @@ -62,14 +73,58 @@ function exceptionHandler($exception) { } elseif (exceptionIsOfType($exception, \Core\Exceptions\SQLException::class)) { /* @var $castedException \Core\Exceptions\SQLException */ $castedException = $exception; - print_error("Fought an uncaught SQL exception", sprintf("%s\n\n%s", $castedException->failingQuery, $exception->getTraceAsString())); + + $logId = tryToLog(getLoggingLocation($exception), + "Fought an uncaught SQL exception: " . $castedException->failingQuery, $castedException->getTraceAsString(), + $userContext, $hesk_settings); + + $logIdText = $logId === null ? "Additionally, the error could not be logged! :'(" : "Log ID: {$logId}"; + print_error("SQL Exception", "Fought an uncaught SQL exception. Check the logs for more information. {$logIdText}"); } else { - print_error("Fought an uncaught exception of type " . get_class($exception), sprintf("%s\n\n%s", $exception->getMessage(), $exception->getTraceAsString())); + $logId = tryToLog(getLoggingLocation($exception), + $exception->getMessage(), $exception->getTraceAsString(), + $userContext, $hesk_settings); + + $logIdText = $logId === null ? "Additionally, the error could not be logged! :'(" : "Log ID: {$logId}"; + print_error("Exception Occurred", "Fought an uncaught exception. Check the logs for more information. {$logIdText}"); } - // Log more stuff to logging table if possible; we'll catch any exceptions from this + die(); } +/** + * @param $location string + * @param $message string + * @param $stackTrace string + * @param $userContext \BusinessLogic\Security\UserContext + * @param $heskSettings array + * @return int|null The inserted ID, or null if failed to log + * @internal param Exception $exception + */ +function tryToLog($location, $message, $stackTrace, $userContext, $heskSettings) { + global $applicationContext; + + /* @var $loggingGateway \DataAccess\Logging\LoggingGateway */ + $loggingGateway = $applicationContext->get[\DataAccess\Logging\LoggingGateway::class]; + + try { + return $loggingGateway->logError($location, $message, $stackTrace, $userContext, $heskSettings); + } catch (Exception $squished) { + return null; + } +} + +/** + * @param $exception Exception + * @return string The location of the exception + */ +function getLoggingLocation($exception) { + // http://stackoverflow.com/a/9133897/1509431 + $trace = $exception->getTrace(); + $lastCall = $trace[0]; + return basename($lastCall['file'], '.php'); +} + /** * @param $exception Exception thrown exception * @param $class string The name of the expected exception type diff --git a/install/mods-for-hesk/sql/installSql.php b/install/mods-for-hesk/sql/installSql.php index 6e2f9b11..35467509 100644 --- a/install/mods-for-hesk/sql/installSql.php +++ b/install/mods-for-hesk/sql/installSql.php @@ -958,4 +958,11 @@ function execute303Scripts() { hesk_dbConnect(); updateVersion('3.0.3'); +} + +function execute310Scripts() { + global $hesk_settings; + hesk_dbConnect(); + + executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "logging` ADD COLUMN `stack_trace` TEXT"); } \ No newline at end of file