diff options
author | Sam <8619576+samjf@users.noreply.github.com> | 2022-04-11 12:32:52 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-11 12:32:52 +0300 |
commit | e6182097f4a9005de69f34f82c3f76b5c6162473 (patch) | |
tree | 5b3dbf39461c64f81aefad15f544b81ca7946862 /core | |
parent | f8267dfed779e06f7483d9d952700e8c819cafba (diff) |
Add the request hostname to error & exception logs (#18996)
* Add the hostname to the tracker exception log so multi-tenant domains are easier to debug
* Add hostname to error log to help with debugging
* use Url::getHost and add method to Url class instead
* apply PSR12 code formatting
Co-authored-by: sgiehl <stefan@matomo.org>
Diffstat (limited to 'core')
-rw-r--r-- | core/ExceptionHandler.php | 32 | ||||
-rw-r--r-- | core/Tracker/Response.php | 14 | ||||
-rw-r--r-- | core/Url.php | 57 | ||||
-rw-r--r-- | core/testMinimumPhpVersion.php | 34 |
4 files changed, 95 insertions, 42 deletions
diff --git a/core/ExceptionHandler.php b/core/ExceptionHandler.php index 4a61495a85..ca549622b5 100644 --- a/core/ExceptionHandler.php +++ b/core/ExceptionHandler.php @@ -1,4 +1,5 @@ <?php + /** * Matomo - free/libre analytics platform * @@ -6,6 +7,7 @@ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later * */ + namespace Piwik; use DI\DependencyException; @@ -25,7 +27,7 @@ class ExceptionHandler { public static function setUp() { - set_exception_handler(array('Piwik\ExceptionHandler', 'handleException')); + set_exception_handler(['Piwik\ExceptionHandler', 'handleException']); } /** @@ -97,7 +99,7 @@ class ExceptionHandler try { echo self::getErrorResponse($exception); - } catch(Exception $e) { + } catch (Exception $e) { // When there are failures while generating the HTML error response itself, // we simply print out the error message instead. echo $exception->getMessage(); @@ -118,11 +120,9 @@ class ExceptionHandler $isHtmlMessage = method_exists($ex, 'isHtmlMessage') && $ex->isHtmlMessage(); if (!$isHtmlMessage && Request::isApiRequest($_GET)) { - $outputFormat = strtolower(Common::getRequestVar('format', 'xml', 'string', $_GET + $_POST)); $response = new ResponseBuilder($outputFormat); return $response->getResponseException($ex); - } elseif (!$isHtmlMessage) { $message = Common::sanitizeInputValue($message); } @@ -150,8 +150,20 @@ class ExceptionHandler // be written to the application log instead $writeErrorLog = !($ex instanceof \Piwik\Exception\NotSupportedBrowserException); - $result = Piwik_GetErrorMessagePage($message, $debugTrace, true, true, $logoHeaderUrl, - $logoFaviconUrl, null, $writeErrorLog); + $hostname = Url::getRFCValidHostname(); + $hostStr = $hostname ? "[$hostname] " : '- '; + + $result = Piwik_GetErrorMessagePage( + $message, + $debugTrace, + true, + true, + $logoHeaderUrl, + $logoFaviconUrl, + null, + $hostStr, + $writeErrorLog + ); try { /** @@ -163,7 +175,7 @@ class ExceptionHandler * @param string &$result The HTML of the error page. * @param Exception $ex The Exception displayed in the error page. */ - Piwik::postEvent('FrontController.modifyErrorPage', array(&$result, $ex)); + Piwik::postEvent('FrontController.modifyErrorPage', [&$result, $ex]); } catch (ContainerDoesNotExistException $ex) { // this can happen when an error occurs before the Piwik environment is created } @@ -171,17 +183,17 @@ class ExceptionHandler return $result; } - private static function logException($exception, $loglevel=Log::ERROR) + private static function logException($exception, $loglevel = Log::ERROR) { try { switch ($loglevel) { - case(Log::DEBUG): + case (Log::DEBUG): StaticContainer::get(LoggerInterface::class)->debug('Uncaught exception: {exception}', [ 'exception' => $exception, 'ignoreInScreenWriter' => true, ]); break; - case(Log::ERROR): + case (Log::ERROR): default: StaticContainer::get(LoggerInterface::class)->error('Uncaught exception: {exception}', [ 'exception' => $exception, diff --git a/core/Tracker/Response.php b/core/Tracker/Response.php index 9e1a63c846..e99feb8b38 100644 --- a/core/Tracker/Response.php +++ b/core/Tracker/Response.php @@ -1,4 +1,5 @@ <?php + /** * Matomo - free/libre analytics platform * @@ -6,6 +7,7 @@ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later * */ + namespace Piwik\Tracker; use Exception; @@ -15,6 +17,7 @@ use Piwik\Profiler; use Piwik\Timer; use Piwik\Tracker; use Piwik\Tracker\Db as TrackerDb; +use Piwik\Url; class Response { @@ -89,7 +92,8 @@ class Response Common::printDebug("End of the page."); - if ($tracker->isDebugModeEnabled() + if ( + $tracker->isDebugModeEnabled() && $tracker->isDatabaseConnected() && TrackerDb::isProfilingEnabled() ) { @@ -190,7 +194,7 @@ class Response // Base64 image string $img = base64_decode($customImage); $size = getimagesizefromstring($img); - } else if (is_file($customImage) && is_readable($customImage)) { + } elseif (is_file($customImage) && is_readable($customImage)) { // Image file $img = file_get_contents($customImage); $size = getimagesize($customImage); // imagesize is used to get the mime type @@ -198,7 +202,7 @@ class Response // Must have valid image data and a valid mime type to proceed if ($img && $size && isset($size['mime']) && in_array($size['mime'], $supportedMimeTypes)) { - Common::sendHeader('Content-Type: '.$size['mime']); + Common::sendHeader('Content-Type: ' . $size['mime']); echo $img; return true; } @@ -229,6 +233,8 @@ class Response protected function logExceptionToErrorLog($e) { - error_log(sprintf("Error in Matomo (tracker): %s", str_replace("\n", " ", $this->getMessageFromException($e)))); + $hostname = Url::getRFCValidHostname(); + $hostStr = $hostname ? "[$hostname]" : '-'; + error_log(sprintf("$hostStr Error in Matomo (tracker): %s", str_replace("\n", " ", $this->getMessageFromException($e)))); } } diff --git a/core/Url.php b/core/Url.php index c027538cdb..abe1c82006 100644 --- a/core/Url.php +++ b/core/Url.php @@ -1,4 +1,5 @@ <?php + /** * Matomo - free/libre analytics platform * @@ -6,6 +7,7 @@ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later * */ + namespace Piwik; use Exception; @@ -129,7 +131,8 @@ class Url $url = ''; // insert extra path info if proxy_uri_header is set and enabled - if (isset(Config::getInstance()->General['proxy_uri_header']) + if ( + isset(Config::getInstance()->General['proxy_uri_header']) && Config::getInstance()->General['proxy_uri_header'] == 1 && !empty($_SERVER['HTTP_X_FORWARDED_URI']) ) { @@ -209,7 +212,8 @@ class Url public static function isValidHost($host = false): bool { // only do trusted host check if it's enabled - if (isset(Config::getInstance()->General['enable_trusted_host_check']) + if ( + isset(Config::getInstance()->General['enable_trusted_host_check']) && Config::getInstance()->General['enable_trusted_host_check'] == 0 ) { return true; @@ -277,12 +281,13 @@ class Url protected static function saveHostsnameInConfig($host, $domain, $key) { - if (Piwik::hasUserSuperUserAccess() + if ( + Piwik::hasUserSuperUserAccess() && file_exists(Config::getLocalConfigPath()) ) { $config = Config::getInstance()->$domain; if (!is_array($host)) { - $host = array($host); + $host = [$host]; } $host = array_filter($host); if (empty($host)) { @@ -340,6 +345,21 @@ class Url } /** + * Returns the valid hostname (according to RFC standards) as a string; else it will return false if it isn't valid. + * If the hostname isn't supplied it will default to using Url::getHost + * Note: this will not verify if the hostname is trusted. + * @param $hostname + * @return false|string + */ + public static function getRFCValidHostname($hostname = null) + { + if (empty($hostname)) { + $hostname = self::getHost(false); + } + return filter_var($hostname, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME); + } + + /** * Sets the host. Useful for CLI scripts, eg. core:archive command * * @param $host string @@ -363,7 +383,7 @@ class Url */ public static function getCurrentHost($default = 'unknown', $checkTrustedHost = true) { - $hostHeaders = array(); + $hostHeaders = []; $config = Config::getInstance()->General; if (isset($config['proxy_host_headers'])) { @@ -371,7 +391,7 @@ class Url } if (!is_array($hostHeaders)) { - $hostHeaders = array(); + $hostHeaders = []; } $host = self::getHost($checkTrustedHost); @@ -390,7 +410,8 @@ class Url public static function getCurrentQueryString() { $url = ''; - if (isset($_SERVER['QUERY_STRING']) + if ( + isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING']) ) { $url .= "?" . $_SERVER['QUERY_STRING']; @@ -491,7 +512,8 @@ class Url private static function redirectToUrlNoExit($url) { - if (UrlHelper::isLookLikeUrl($url) + if ( + UrlHelper::isLookLikeUrl($url) || strpos($url, 'index.php') === 0 ) { Common::sendResponseCode(302); @@ -568,13 +590,13 @@ class Url // handle host name mangling $requestUri = isset($_SERVER['SCRIPT_URI']) ? $_SERVER['SCRIPT_URI'] : ''; $parseRequest = @parse_url($requestUri); - $hosts = array(self::getHost(), self::getCurrentHost()); + $hosts = [self::getHost(), self::getCurrentHost()]; if (!empty($parseRequest['host'])) { $hosts[] = $parseRequest['host']; } // drop port numbers from hostnames and IP addresses - $hosts = array_map(array('self', 'getHostSanitized'), $hosts); + $hosts = array_map(['self', 'getHostSanitized'], $hosts); $disableHostCheck = Config::getInstance()->General['enable_trusted_host_check'] == 0; // compare scheme and host @@ -583,7 +605,7 @@ class Url return !empty($host) && ($disableHostCheck || in_array($host, $hosts)) && !empty($parsedUrl['scheme']) - && in_array($parsedUrl['scheme'], array('http', 'https')); + && in_array($parsedUrl['scheme'], ['http', 'https']); } /** @@ -650,12 +672,12 @@ class Url $config = @Config::getInstance()->$domain; if (!isset($config[$key])) { - return array(); + return []; } $hosts = $config[$key]; if (!is_array($hosts)) { - return array(); + return []; } return $hosts; } @@ -735,7 +757,7 @@ class Url */ public static function getLocalHostnames() { - return array('localhost', '127.0.0.1', '::1', '[::1]', '[::]', '0000::1', '0177.0.0.1', '2130706433', '[0:0:0:0:0:ffff:127.0.0.1]'); + return ['localhost', '127.0.0.1', '::1', '[::1]', '[::]', '0000::1', '0177.0.0.1', '2130706433', '[0:0:0:0:0:ffff:127.0.0.1]']; } /** @@ -769,10 +791,10 @@ class Url return 'http'; } - if ((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] === true)) + if ( + (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] === true)) || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') ) { - return 'https'; } return 'http'; @@ -796,7 +818,8 @@ class Url { $host = @$_SERVER['SERVER_NAME']; if (!empty($host)) { - if (strpos($host, ':') === false + if ( + strpos($host, ':') === false && !empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443 diff --git a/core/testMinimumPhpVersion.php b/core/testMinimumPhpVersion.php index 70fa5310bf..efeb530f68 100644 --- a/core/testMinimumPhpVersion.php +++ b/core/testMinimumPhpVersion.php @@ -1,4 +1,5 @@ <?php + /** * Matomo - free/libre analytics platform * @@ -61,12 +62,12 @@ if ($minimumPhpInvalid) { $composerInstall = "Download and run <a href=\"https://getcomposer.org/Composer-Setup.exe\"><b>Composer-Setup.exe</b></a>, it will install the latest Composer version and set up your PATH so that you can just call composer from any directory in your command line. " . " <br>Then run this command in a terminal in the matomo directory: <br> $ php composer.phar install "; } - $piwik_errorMessage .= "<p>It appears the <a href='https://getcomposer.org/' rel='noreferrer noopener' target='_blank'>composer</a> tool is not yet installed. You can install Composer in a few easy steps:\n\n". - "<br/>" . $composerInstall. - " This will initialize composer for Matomo and download libraries we use in vendor/* directory.". + $piwik_errorMessage .= "<p>It appears the <a href='https://getcomposer.org/' rel='noreferrer noopener' target='_blank'>composer</a> tool is not yet installed. You can install Composer in a few easy steps:\n\n" . + "<br/>" . $composerInstall . + " This will initialize composer for Matomo and download libraries we use in vendor/* directory." . "\n\n<br/><br/>Then reload this page to access your analytics reports." . "\n\n<br/><br/>For more information check out this FAQ: <a href='https://matomo.org/faq/how-to-install/faq_18271/' rel='noreferrer noopener' target='_blank'>How do I use Matomo from the Git repository?</a>." . - "\n\n<br/><br/>Note: if for some reasons you cannot install composer, instead install the latest Matomo release from ". + "\n\n<br/><br/>Note: if for some reasons you cannot install composer, instead install the latest Matomo release from " . "<a href='https://builds.matomo.org/piwik.zip' rel='noreferrer noopener'>builds.matomo.org</a>.</p>"; } } @@ -83,7 +84,8 @@ if (!function_exists('Piwik_GetErrorMessagePage')) { */ function Piwik_ShouldPrintBackTraceWithMessage() { - if (class_exists('\Piwik\SettingsServer') + if ( + class_exists('\Piwik\SettingsServer') && class_exists('\Piwik\Common') && \Piwik\SettingsServer::isArchivePhpTriggered() && \Piwik\Common::isPhpCliMode() @@ -109,14 +111,23 @@ if (!function_exists('Piwik_GetErrorMessagePage')) { * @param bool $optionalLinkBack If true, displays a link to go back * @param bool|string $logoUrl The URL to the logo to use. * @param bool|string $faviconUrl The URL to the favicon to use. + * @param string $errorLogPrefix String to prepend to the error in log file * @param bool $writeErrorLog If true then a webserver error log will be written, defaults to true * @return string */ - function Piwik_GetErrorMessagePage($message, $optionalTrace = false, $optionalLinks = false, $optionalLinkBack = false, - $logoUrl = false, $faviconUrl = false, $isCli = null, bool $writeErrorLog = true) - { + function Piwik_GetErrorMessagePage( + $message, + $optionalTrace = false, + $optionalLinks = false, + $optionalLinkBack = false, + $logoUrl = false, + $faviconUrl = false, + $isCli = null, + $errorLogPrefix = '', + bool $writeErrorLog = true + ) { if ($writeErrorLog) { - error_log(sprintf("Error in Matomo: %s", str_replace("\n", " ", strip_tags($message)))); + error_log(sprintf("${errorLogPrefix}Error in Matomo: %s", str_replace("\n", " ", strip_tags($message)))); } if (!headers_sent()) { @@ -130,7 +141,8 @@ if (!function_exists('Piwik_GetErrorMessagePage')) { } // We return only an HTML fragment for AJAX requests - if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) + if ( + isset($_SERVER['HTTP_X_REQUESTED_WITH']) && (strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') ) { return "<div class='alert alert-danger'><strong>Error:</strong> $message</div>"; @@ -182,7 +194,7 @@ if (!function_exists('Piwik_GetErrorMessagePage')) { . ' ' . $optionalLinks; - $message = str_replace(array("<br />", "<br>", "<br/>", "</p>"), "\n", $message); + $message = str_replace(["<br />", "<br>", "<br/>", "</p>"], "\n", $message); $message = str_replace("\t", "", $message); $message = strip_tags($message); |