diff --git a/application/ManiaControl.php b/application/ManiaControl.php index 9a6c1de8..aaefbba5 100644 --- a/application/ManiaControl.php +++ b/application/ManiaControl.php @@ -108,6 +108,9 @@ if (extension_loaded('curl')) { exit(); } +// Make sure garbage collection is enabled +gc_enable(); + // Autoload Function that loads ManiaControl Class Files on Demand spl_autoload_register(function ($className) { $classPath = str_replace('\\', DIRECTORY_SEPARATOR, $className); diff --git a/application/core/ErrorHandler.php b/application/core/ErrorHandler.php index 2a202017..1a2ce336 100644 --- a/application/core/ErrorHandler.php +++ b/application/core/ErrorHandler.php @@ -2,9 +2,11 @@ namespace ManiaControl; +use ManiaControl\Callbacks\Callbacks; use ManiaControl\Files\FileUtil; use ManiaControl\Plugins\PluginManager; use ManiaControl\Update\UpdateManager; +use Maniaplanet\DedicatedServer\Connection; /** * Error and Exception Manager Class @@ -28,11 +30,14 @@ class ErrorHandler { /** * Construct Error Handler + * + * @param ManiaControl @maniaControl */ public function __construct(ManiaControl $maniaControl) { $this->maniaControl = $maniaControl; set_error_handler(array(&$this, 'handleError'), -1); set_exception_handler(array(&$this, 'handleException')); + register_shutdown_function(array(&$this, 'handleShutdown')); } /** @@ -42,6 +47,278 @@ class ErrorHandler { $this->maniaControl->settingManager->initSetting($this, self::SETTING_RESTART_ON_EXCEPTION, true); } + /** + * Trigger a Debug Notice to the ManiaControl Website + * + * @param string $message + */ + public function triggerDebugNotice($message) { + $this->handleError(self::MC_DEBUG_NOTICE, $message); + } + + /** + * ManiaControl Error Handler + * + * @param int $errorNumber + * @param string $errorString + * @param string $errorFile + * @param int $errorLine + * @param array $errorContext + * @param bool $onShutdown + * @return bool + */ + public function handleError($errorNumber, $errorString, $errorFile = null, $errorLine = -1, array $errorContext = array(), $onShutdown = false) { + $suppressed = (error_reporting() === 0); + if ($suppressed && !self::LOG_SUPPRESSED_ERRORS) { + return false; + } + + $errorTag = $this->getErrorTag($errorNumber); + $userError = $this->isUserErrorNumber($errorNumber); + $traceSourceClass = null; + + $message = $errorTag . ': ' . $errorString; + $fileLine = $errorFile . ': ' . $errorLine; + $traceString = $this->parseBackTrace(array_slice(debug_backtrace(), 1), $traceSourceClass); + $sourceClass = $this->getSourceClass($errorFile); + if (!$sourceClass) { + $sourceClass = $traceSourceClass; + } + + $logMessage = $message . PHP_EOL . 'File&Line: ' . $fileLine; + if (!$userError && !$onShutdown) { + $logMessage .= PHP_EOL . 'Trace: ' . PHP_EOL . $traceString; + } + $this->maniaControl->log($logMessage); + + if (!DEV_MODE && !$userError && !$suppressed) { + $report = array(); + $report['Type'] = 'Error'; + $report['Message'] = $message; + $report['FileLine'] = $fileLine; + $report['SourceClass'] = $sourceClass; + $report['PluginId'] = PluginManager::getPluginId($sourceClass); + if (!$onShutdown) { + $report['Backtrace'] = $traceString; + } + $report['OperatingSystem'] = php_uname(); + $report['PHPVersion'] = phpversion(); + + if ($this->maniaControl->server) { + $report['ServerLogin'] = $this->maniaControl->server->login; + } + + if ($this->maniaControl->settingManager && $this->maniaControl->updateManager) { + $report['UpdateChannel'] = $this->maniaControl->settingManager->getSettingValue($this->maniaControl->updateManager, UpdateManager::SETTING_UPDATECHECK_CHANNEL); + $report['ManiaControlVersion'] = ManiaControl::VERSION . ' ' . $this->maniaControl->updateManager->getNightlyBuildDate(); + } else { + $report['ManiaControlVersion'] = ManiaControl::VERSION; + } + + $json = json_encode($report); + $info = base64_encode($json); + + $url = ManiaControl::URL_WEBSERVICE . 'errorreport?error=' . urlencode($info); + $response = FileUtil::loadFile($url); + $success = json_decode($response); + if ($success) { + logMessage('Error-Report successful!'); + } else { + logMessage('Error-Report failed! ' . print_r($response, true)); + } + } + if ($this->shouldStopExecution($errorNumber)) { + $this->maniaControl->quit('Stopping Execution...'); + } + return false; + } + + /** + * Get the Prefix for the given Error Level + * + * @param int $errorLevel + * @return string + */ + public function getErrorTag($errorLevel) { + switch ($errorLevel) { + case E_NOTICE: + return '[PHP NOTICE]'; + case E_WARNING: + return '[PHP WARNING]'; + case E_ERROR: + return '[PHP ERROR]'; + case E_CORE_ERROR: + return '[PHP CORE ERROR]'; + case E_COMPILE_ERROR: + return '[PHP COMPILE ERROR]'; + case E_RECOVERABLE_ERROR: + return '[PHP RECOVERABLE ERROR]'; + case E_USER_NOTICE: + return '[ManiaControl NOTICE]'; + case E_USER_WARNING: + return '[ManiaControl WARNING]'; + case E_USER_ERROR: + return '[ManiaControl ERROR]'; + case self::MC_DEBUG_NOTICE: + return '[ManiaControl DEBUG]'; + } + return "[PHP ERROR '{$errorLevel}']"; + } + + /** + * Check if the given Error Number is a User Error + * + * @param int $errorNumber + * @return bool + */ + private function isUserErrorNumber($errorNumber) { + return ($errorNumber & E_USER_ERROR || $errorNumber & E_USER_WARNING || $errorNumber & E_USER_NOTICE || $errorNumber & E_USER_DEPRECATED); + } + + /** + * Parse the Debug Backtrace into a String for the Error Report + * + * @param array $backtrace + * @param string $sourceClass + * @return string + */ + private function parseBackTrace(array $backtrace, &$sourceClass = null) { + $traceString = ''; + $stepCount = 0; + foreach ($backtrace as $traceStep) { + $traceString .= '#' . $stepCount . ': '; + if (isset($traceStep['class'])) { + if (!$sourceClass) { + $sourceClass = $traceStep['class']; + } + $traceString .= $traceStep['class']; + } + if (isset($traceStep['type'])) { + $traceString .= $traceStep['type']; + } + if (isset($traceStep['function'])) { + $traceString .= $traceStep['function'] . '('; + if (isset($traceStep['args'])) { + $traceString .= $this->parseArgumentsArray($traceStep['args']); + } + $traceString .= ')'; + } + if (isset($traceStep['file'])) { + $traceString .= ' in File '; + $traceString .= $traceStep['file']; + } + if (isset($traceStep['line'])) { + $traceString .= ' on Line '; + $traceString .= $traceStep['line']; + } + $traceString .= PHP_EOL; + if (strlen($traceString) > 1300) { + // Too long... + $traceString .= '...'; + break; + } + $stepCount++; + } + return $traceString; + } + + /** + * Build a String from an Arguments Array + * + * @param array $args + * @return string + */ + private function parseArgumentsArray(array $args) { + $string = ''; + $argsCount = count($args); + foreach ($args as $index => $arg) { + if (is_object($arg)) { + $string .= 'object(' . get_class($arg) . ')'; + } else if (is_array($arg)) { + $string .= 'array(' . $this->parseArgumentsArray($arg) . ')'; + } else { + $type = gettype($arg); + $string .= $type . '(' . print_r($arg, true) . ')'; + } + if ($index < $argsCount - 1) { + $string .= ', '; + } + if (strlen($string) > 100) { + // Too long... + $string .= '...'; + break; + } + } + return $string; + } + + /** + * Get the Source Class via the Error File + * + * @param string $errorFile + * @return string + */ + private function getSourceClass($errorFile) { + if (!$errorFile) { + return null; + } + $filePath = substr($errorFile, strlen(ManiaControlDir)); + $filePath = str_replace('plugins' . DIRECTORY_SEPARATOR, '', $filePath); + $filePath = str_replace('core' . DIRECTORY_SEPARATOR, 'ManiaControl\\', $filePath); + $className = str_replace('.php', '', $filePath); + $className = str_replace(DIRECTORY_SEPARATOR, '\\', $className); + if (!class_exists($className, false)) { + return null; + } + return $className; + } + + /** + * Test if ManiaControl should stop its Execution + * + * @param int $errorNumber + * @return bool + */ + private function shouldStopExecution($errorNumber) { + return ($errorNumber & E_FATAL); + } + + /** + * Handle PHP Process Shutdown + */ + public function handleShutdown() { + // TODO: skip client-related actions on transport exception (e.g. server down) + + if ($this->maniaControl->callbackManager) { + // OnShutdown callback + $this->maniaControl->callbackManager->triggerCallback(Callbacks::ONSHUTDOWN); + } + + if ($this->maniaControl->chat) { + // Announce quit + $this->maniaControl->chat->sendInformation('ManiaControl shutting down.'); + } + + if ($this->maniaControl->client) { + try { + // Hide manialinks + $this->maniaControl->client->sendHideManialinkPage(); + // Close the client connection + Connection::delete($this->maniaControl->client); + } catch (TransportException $e) { + $this->handleException($e, false); + } + } + + // Check if the Shutdown was caused by a Fatal Error and report it + $error = error_get_last(); + if ($error && ($error['type'] & E_FATAL)) { + $this->handleError($error['type'], $error['message'], $error['file'], $error['line'], array(), true); + } + + $this->maniaControl->quit('Quitting ManiaControl!'); + } + /** * ManiaControl Exception Handler * @@ -102,82 +379,6 @@ class ErrorHandler { } } - /** - * Parse the Debug Backtrace into a String for the Error Report - * - * @param array $backtrace - * @param string $sourceClass - * @return string - */ - private function parseBackTrace(array $backtrace, &$sourceClass = null) { - $traceString = ''; - $stepCount = 0; - foreach ($backtrace as $traceStep) { - $traceString .= PHP_EOL . '#' . $stepCount . ': '; - if (isset($traceStep['class'])) { - if (!$sourceClass) { - $sourceClass = $traceStep['class']; - } - $traceString .= $traceStep['class']; - } - if (isset($traceStep['type'])) { - $traceString .= $traceStep['type']; - } - if (isset($traceStep['function'])) { - $traceString .= $traceStep['function'] . '('; - if (isset($traceStep['args'])) { - $traceString .= $this->parseArgumentsArray($traceStep['args']); - } - $traceString .= ')'; - } - if (isset($traceStep['file'])) { - $traceString .= ' in File '; - $traceString .= $traceStep['file']; - } - if (isset($traceStep['line'])) { - $traceString .= ' on Line '; - $traceString .= $traceStep['line']; - } - if (strlen($traceString) > 1300) { - // Too long... - $traceString .= '...'; - break; - } - $stepCount++; - } - return $traceString; - } - - /** - * Build a String from an Arguments Array - * - * @param array $args - * @return string - */ - private function parseArgumentsArray(array $args) { - $string = ''; - $argsCount = count($args); - foreach ($args as $index => $arg) { - if (is_object($arg)) { - $string .= 'object(' . get_class($arg) . ')'; - } else if (is_array($arg)) { - $string .= 'array(' . $this->parseArgumentsArray($arg) . ')'; - } else { - $type = gettype($arg); - $string .= $type . '(' . print_r($arg, true) . ')'; - } - if ($index < $argsCount - 1) { - $string .= ', '; - } - if (strlen($string) > 100) { - // Too long... - $string .= '...'; - break; - } - } - return $string; - } - /** * Test if ManiaControl should restart automatically * @@ -190,170 +391,4 @@ class ErrorHandler { $setting = $this->maniaControl->settingManager->getSettingValue($this, self::SETTING_RESTART_ON_EXCEPTION, true); return $setting; } - - /** - * Trigger a Debug Notice to the ManiaControl Website - * - * @param string $message - */ - public function triggerDebugNotice($message) { - $this->handleError(self::MC_DEBUG_NOTICE, $message); - } - - /** - * ManiaControl Error Handler - * - * @param int $errorNumber - * @param string $errorString - * @param string $errorFile - * @param int $errorLine - * @param array $errorContext - * @return bool - */ - public function handleError($errorNumber, $errorString, $errorFile = null, $errorLine = -1, array $errorContext = array()) { - $suppressed = (error_reporting() === 0); - if ($suppressed && !self::LOG_SUPPRESSED_ERRORS) { - return false; - } - - $errorTag = $this->getErrorTag($errorNumber); - $userError = $this->isUserErrorNumber($errorNumber); - $traceSourceClass = null; - - $message = $errorTag . ': ' . $errorString; - $fileLine = $errorFile . ': ' . $errorLine; - $traceString = $this->parseBackTrace(array_slice(debug_backtrace(), 1), $traceSourceClass); - $sourceClass = $this->getSourceClass($errorFile); - if (!$sourceClass) { - $sourceClass = $traceSourceClass; - } - - $logMessage = $message . PHP_EOL . 'File&Line: ' . $fileLine; - if (!$userError) { - $logMessage .= PHP_EOL . 'Trace: ' . $traceString; - } - $this->maniaControl->log($logMessage); - - if (!DEV_MODE && !$userError && !$suppressed) { - $report = array(); - $report['Type'] = 'Error'; - $report['Message'] = $message; - $report['FileLine'] = $fileLine; - $report['SourceClass'] = $sourceClass; - $report['PluginId'] = PluginManager::getPluginId($sourceClass); - $report['Backtrace'] = $traceString; - $report['OperatingSystem'] = php_uname(); - $report['PHPVersion'] = phpversion(); - - if ($this->maniaControl->server) { - $report['ServerLogin'] = $this->maniaControl->server->login; - } - - if ($this->maniaControl->settingManager && $this->maniaControl->updateManager) { - $report['UpdateChannel'] = $this->maniaControl->settingManager->getSettingValue($this->maniaControl->updateManager, UpdateManager::SETTING_UPDATECHECK_CHANNEL); - $report['ManiaControlVersion'] = ManiaControl::VERSION . ' ' . $this->maniaControl->updateManager->getNightlyBuildDate(); - } else { - $report['ManiaControlVersion'] = ManiaControl::VERSION; - } - - $json = json_encode($report); - $info = base64_encode($json); - - $url = ManiaControl::URL_WEBSERVICE . 'errorreport?error=' . urlencode($info); - $response = FileUtil::loadFile($url); - $success = json_decode($response); - if ($success) { - logMessage('Error successfully reported!'); - } else { - logMessage('Error-Report failed! ' . print_r($response, true)); - } - } - if ($this->shouldStopExecution($errorNumber)) { - $this->maniaControl->quit('Stopping Execution...'); - } - return false; - } - - /** - * Get the Prefix for the given Error Level - * - * @param int $errorLevel - * @return string - */ - public function getErrorTag($errorLevel) { - switch ($errorLevel) { - case E_NOTICE: - return '[PHP NOTICE]'; - case E_WARNING: - return '[PHP WARNING]'; - case E_ERROR: - return '[PHP ERROR]'; - case E_CORE_ERROR: - return '[PHP CORE ERROR]'; - case E_COMPILE_ERROR: - return '[PHP COMPILE ERROR]'; - case E_RECOVERABLE_ERROR: - return '[PHP RECOVERABLE ERROR]'; - case E_USER_NOTICE: - return '[ManiaControl NOTICE]'; - case E_USER_WARNING: - return '[ManiaControl WARNING]'; - case E_USER_ERROR: - return '[ManiaControl ERROR]'; - case self::MC_DEBUG_NOTICE: - return '[ManiaControl DEBUG]'; - } - return "[PHP ERROR '{$errorLevel}']"; - } - - /** - * Check if the given Error Number is a User Error - * - * @param int $errorNumber - * @return bool - */ - private function isUserErrorNumber($errorNumber) { - return ($errorNumber & E_USER_ERROR || $errorNumber & E_USER_WARNING || $errorNumber & E_USER_NOTICE || $errorNumber & E_USER_DEPRECATED); - } - - /** - * Get the Source Class via the Error File - * - * @param string $errorFile - * @return string - */ - private function getSourceClass($errorFile) { - if (!$errorFile) { - return null; - } - $filePath = substr($errorFile, strlen(ManiaControlDir)); - $filePath = str_replace('plugins' . DIRECTORY_SEPARATOR, '', $filePath); - $filePath = str_replace('core' . DIRECTORY_SEPARATOR, 'ManiaControl\\', $filePath); - $className = str_replace('.php', '', $filePath); - $className = str_replace(DIRECTORY_SEPARATOR, '\\', $className); - if (!class_exists($className, false)) { - return null; - } - return $className; - } - - /** - * Test if ManiaControl should stop its Execution - * - * @param int $errorNumber - * @return bool - */ - private function shouldStopExecution($errorNumber) { - return ($errorNumber & E_FATAL); - } - - /** - * Check if the Shutdown was caused by a Fatal Error and report it - */ - public function handleShutdown() { - $error = error_get_last(); - if ($error && ($error['type'] & E_FATAL)) { - $this->handleError($error['type'], $error['message'], $error['file'], $error['line']); - } - } } \ No newline at end of file diff --git a/application/core/ManiaControl.php b/application/core/ManiaControl.php index 4100fae2..bfba5511 100644 --- a/application/core/ManiaControl.php +++ b/application/core/ManiaControl.php @@ -121,6 +121,8 @@ class ManiaControl implements CommandListener, TimerListener { $this->pluginManager = new PluginManager($this); $this->updateManager = new UpdateManager($this); + $this->errorHandler->init(); + // Define Permission Levels $this->authenticationManager->definePermissionLevel(self::SETTING_PERMISSION_SHUTDOWN, AuthenticationManager::AUTH_LEVEL_SUPERADMIN); $this->authenticationManager->definePermissionLevel(self::SETTING_PERMISSION_RESTART, AuthenticationManager::AUTH_LEVEL_SUPERADMIN); @@ -132,8 +134,6 @@ class ManiaControl implements CommandListener, TimerListener { // Check connection every 30 seconds $this->timerManager->registerTimerListening($this, 'checkConnection', 1000 * 30); - - $this->errorHandler->init(); } /** @@ -282,51 +282,12 @@ class ManiaControl implements CommandListener, TimerListener { $this->quit("ManiaControl Shutdown requested by '{$player->login}'!"); } - /** - * Handle PHP Process Shutdown - */ - public function handleShutdown() { - // OnShutdown callback - $this->callbackManager->triggerCallback(Callbacks::ONSHUTDOWN); - - // Announce quit - // TODO: skip client-related actions on transport exception (e.g. server down) - $this->chat->sendInformation('ManiaControl shutting down.'); - - if ($this->client) { - try { - // Hide manialinks - $this->client->sendHideManialinkPage(); - // Close the client connection - $this->client->delete($this->server->ip, $this->server->port); - } catch (TransportException $e) { - $this->errorHandler->handleException($e, false); - } - } - - $this->errorHandler->handleShutdown(); - - $this->log('Quitting ManiaControl!'); - exit(); - } - - /** - * Collect Garbage - */ - public function collectGarbage() { - // TODO: remove after a check of the influence - // gc_collect_cycles(); - } - /** * Run ManiaControl */ public function run() { $this->log('Starting ManiaControl v' . self::VERSION . '!'); - // Register shutdown handler - register_shutdown_function(array($this, 'handleShutdown')); - // Connect to server $this->connect(); @@ -346,10 +307,6 @@ class ManiaControl implements CommandListener, TimerListener { // AfterInit callback $this->callbackManager->triggerCallback(Callbacks::AFTERINIT); - // Enable Garbage Collecting - gc_enable(); - $this->timerManager->registerTimerListening($this, 'collectGarbage', 1000 * 60); - // Announce ManiaControl $this->chat->sendInformation('ManiaControl v' . self::VERSION . ' successfully started!');