diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index d04a186036..db623969d6 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -974,6 +974,7 @@ return array( 'OC\\Log\\Errorlog' => $baseDir . '/lib/private/Log/Errorlog.php', 'OC\\Log\\ExceptionSerializer' => $baseDir . '/lib/private/Log/ExceptionSerializer.php', 'OC\\Log\\File' => $baseDir . '/lib/private/Log/File.php', + 'OC\\Log\\LogDetails' => $baseDir . '/lib/private/Log/LogDetails.php', 'OC\\Log\\LogFactory' => $baseDir . '/lib/private/Log/LogFactory.php', 'OC\\Log\\Rotate' => $baseDir . '/lib/private/Log/Rotate.php', 'OC\\Log\\Syslog' => $baseDir . '/lib/private/Log/Syslog.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 8cd007f513..e7d4a79da9 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1008,6 +1008,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Log\\Errorlog' => __DIR__ . '/../../..' . '/lib/private/Log/Errorlog.php', 'OC\\Log\\ExceptionSerializer' => __DIR__ . '/../../..' . '/lib/private/Log/ExceptionSerializer.php', 'OC\\Log\\File' => __DIR__ . '/../../..' . '/lib/private/Log/File.php', + 'OC\\Log\\LogDetails' => __DIR__ . '/../../..' . '/lib/private/Log/LogDetails.php', 'OC\\Log\\LogFactory' => __DIR__ . '/../../..' . '/lib/private/Log/LogFactory.php', 'OC\\Log\\Rotate' => __DIR__ . '/../../..' . '/lib/private/Log/Rotate.php', 'OC\\Log\\Syslog' => __DIR__ . '/../../..' . '/lib/private/Log/Syslog.php', diff --git a/lib/private/Log/File.php b/lib/private/Log/File.php index 6810e2598c..fe2bbf30a2 100644 --- a/lib/private/Log/File.php +++ b/lib/private/Log/File.php @@ -47,7 +47,7 @@ use OCP\ILogger; * Log is saved at data/nextcloud.log (on default) */ -class File implements IWriter, IFileBased { +class File extends LogDetails implements IWriter, IFileBased { /** @var string */ protected $logFile; /** @var int */ @@ -56,6 +56,7 @@ class File implements IWriter, IFileBased { private $config; public function __construct(string $path, string $fallbackPath = '', SystemConfig $config) { + parent::__construct($config); $this->logFile = $path; if (!file_exists($this->logFile)) { if( @@ -79,63 +80,7 @@ class File implements IWriter, IFileBased { * @param int $level */ public function write(string $app, $message, int $level) { - // default to ISO8601 - $format = $this->config->getValue('logdateformat', \DateTime::ATOM); - $logTimeZone = $this->config->getValue('logtimezone', 'UTC'); - try { - $timezone = new \DateTimeZone($logTimeZone); - } catch (\Exception $e) { - $timezone = new \DateTimeZone('UTC'); - } - $time = \DateTime::createFromFormat("U.u", number_format(microtime(true), 4, ".", "")); - if ($time === false) { - $time = new \DateTime(null, $timezone); - } else { - // apply timezone if $time is created from UNIX timestamp - $time->setTimezone($timezone); - } - $request = \OC::$server->getRequest(); - $reqId = $request->getId(); - $remoteAddr = $request->getRemoteAddress(); - // remove username/passwords from URLs before writing the to the log file - $time = $time->format($format); - $url = ($request->getRequestUri() !== '') ? $request->getRequestUri() : '--'; - $method = is_string($request->getMethod()) ? $request->getMethod() : '--'; - if($this->config->getValue('installed', false)) { - $user = \OC_User::getUser() ? \OC_User::getUser() : '--'; - } else { - $user = '--'; - } - $userAgent = $request->getHeader('User-Agent'); - if ($userAgent === '') { - $userAgent = '--'; - } - $version = $this->config->getValue('version', ''); - $entry = compact( - 'reqId', - 'level', - 'time', - 'remoteAddr', - 'user', - 'app', - 'method', - 'url', - 'message', - 'userAgent', - 'version' - ); - // PHP's json_encode only accept proper UTF-8 strings, loop over all - // elements to ensure that they are properly UTF-8 compliant or convert - // them manually. - foreach($entry as $key => $value) { - if(is_string($value)) { - $testEncode = json_encode($value); - if($testEncode === false) { - $entry[$key] = utf8_encode($value); - } - } - } - $entry = json_encode($entry, JSON_PARTIAL_OUTPUT_ON_ERROR); + $entry = $this->logDetailsAsJSON($app, $message, $level); $handle = @fopen($this->logFile, 'a'); if ($this->logFileMode > 0 && (fileperms($this->logFile) & 0777) != $this->logFileMode) { @chmod($this->logFile, $this->logFileMode); diff --git a/lib/private/Log/LogDetails.php b/lib/private/Log/LogDetails.php new file mode 100644 index 0000000000..712b5403ca --- /dev/null +++ b/lib/private/Log/LogDetails.php @@ -0,0 +1,101 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Log; + +use OC\SystemConfig; + +abstract class LogDetails { + + /** @var SystemConfig */ + private $config; + + public function __construct(SystemConfig $config) { + $this->config = $config; + } + + public function logDetails(string $app, $message, int $level): array { + // default to ISO8601 + $format = $this->config->getValue('logdateformat', \DateTime::ATOM); + $logTimeZone = $this->config->getValue('logtimezone', 'UTC'); + try { + $timezone = new \DateTimeZone($logTimeZone); + } catch (\Exception $e) { + $timezone = new \DateTimeZone('UTC'); + } + $time = \DateTime::createFromFormat("U.u", number_format(microtime(true), 4, ".", "")); + if ($time === false) { + $time = new \DateTime(null, $timezone); + } else { + // apply timezone if $time is created from UNIX timestamp + $time->setTimezone($timezone); + } + $request = \OC::$server->getRequest(); + $reqId = $request->getId(); + $remoteAddr = $request->getRemoteAddress(); + // remove username/passwords from URLs before writing the to the log file + $time = $time->format($format); + $url = ($request->getRequestUri() !== '') ? $request->getRequestUri() : '--'; + $method = is_string($request->getMethod()) ? $request->getMethod() : '--'; + if($this->config->getValue('installed', false)) { + $user = \OC_User::getUser() ? \OC_User::getUser() : '--'; + } else { + $user = '--'; + } + $userAgent = $request->getHeader('User-Agent'); + if ($userAgent === '') { + $userAgent = '--'; + } + $version = $this->config->getValue('version', ''); + $entry = compact( + 'reqId', + 'level', + 'time', + 'remoteAddr', + 'user', + 'app', + 'method', + 'url', + 'message', + 'userAgent', + 'version' + ); + return $entry; + } + + public function logDetailsAsJSON(string $app, $message, int $level): string { + $entry = $this->logDetails($app, $message, $level); + // PHP's json_encode only accept proper UTF-8 strings, loop over all + // elements to ensure that they are properly UTF-8 compliant or convert + // them manually. + foreach($entry as $key => $value) { + if(is_string($value)) { + $testEncode = json_encode($value); + if($testEncode === false) { + $entry[$key] = utf8_encode($value); + } + } + } + return json_encode($entry, JSON_PARTIAL_OUTPUT_ON_ERROR); + } +} diff --git a/lib/private/Log/Syslog.php b/lib/private/Log/Syslog.php index b652eb4343..b8fba34d62 100644 --- a/lib/private/Log/Syslog.php +++ b/lib/private/Log/Syslog.php @@ -25,11 +25,11 @@ namespace OC\Log; +use OC\SystemConfig; use OCP\ILogger; -use OCP\IConfig; use OCP\Log\IWriter; -class Syslog implements IWriter { +class Syslog extends LogDetails implements IWriter { protected $levels = [ ILogger::DEBUG => LOG_DEBUG, ILogger::INFO => LOG_INFO, @@ -38,7 +38,8 @@ class Syslog implements IWriter { ILogger::FATAL => LOG_CRIT, ]; - public function __construct(IConfig $config) { + public function __construct(SystemConfig $config) { + parent::__construct($config); openlog($config->getSystemValue('syslog_tag', 'Nextcloud'), LOG_PID | LOG_CONS, LOG_USER); } @@ -54,6 +55,6 @@ class Syslog implements IWriter { */ public function write(string $app, $message, int $level) { $syslog_level = $this->levels[$level]; - syslog($syslog_level, '{'.$app.'} '.$message); + syslog($syslog_level, $this->logDetailsAsJSON($app, $message, $level)); } } diff --git a/lib/private/Log/Systemdlog.php b/lib/private/Log/Systemdlog.php index 40e9c12386..c40e4860f9 100644 --- a/lib/private/Log/Systemdlog.php +++ b/lib/private/Log/Systemdlog.php @@ -23,6 +23,7 @@ namespace OC\Log; use OC\HintException; +use OC\SystemConfig; use OCP\ILogger; use OCP\IConfig; use OCP\Log\IWriter; @@ -42,7 +43,7 @@ use OCP\Log\IWriter; // SYSLOG_FACILITY=, SYSLOG_IDENTIFIER=, SYSLOG_PID= // Syslog compatibility fields -class Systemdlog implements IWriter { +class Systemdlog extends LogDetails implements IWriter { protected $levels = [ ILogger::DEBUG => 7, ILogger::INFO => 6, @@ -53,14 +54,15 @@ class Systemdlog implements IWriter { protected $syslogId; - public function __construct(IConfig $config) { + public function __construct(SystemConfig $config) { + parent::__construct($config); if(!function_exists('sd_journal_send')) { throw new HintException( 'PHP extension php-systemd is not available.', 'Please install and enable PHP extension systemd if you wish to log to the Systemd journal.'); } - $this->syslogId = $config->getSystemValue('syslog_tag', 'Nextcloud'); + $this->syslogId = $config->getValue('syslog_tag', 'Nextcloud'); } /** @@ -73,6 +75,6 @@ class Systemdlog implements IWriter { $journal_level = $this->levels[$level]; sd_journal_send('PRIORITY='.$journal_level, 'SYSLOG_IDENTIFIER='.$this->syslogId, - 'MESSAGE={'.$app.'} '.$message); + 'MESSAGE=' . $this->logDetailsAsJSON($app, $message, $level)); } }