diff --git a/.github/workflows/psalm-matrix.yml b/.github/workflows/psalm-matrix.yml index 192fcc13..c3fa781d 100644 --- a/.github/workflows/psalm-matrix.yml +++ b/.github/workflows/psalm-matrix.yml @@ -24,7 +24,7 @@ jobs: # do not stop on another job's failure fail-fast: false matrix: - ocp-version: [ 'dev-master', 'dev-stable27', 'dev-stable26', 'dev-stable25'] + ocp-version: [ 'dev-master' ] name: Nextcloud ${{ matrix.ocp-version }} steps: diff --git a/appinfo/info.xml b/appinfo/info.xml index a2e2e0af..f80b2869 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -12,6 +12,10 @@ LogReader + + + + tools https://github.com/nextcloud/logreader https://github.com/nextcloud/logreader/issues diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 87a415d4..5f46c8c5 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -23,11 +23,13 @@ namespace OCA\LogReader\AppInfo; +use OCA\LogReader\Listener\LogListener; use OCA\LogReader\Log\Formatter; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; +use OCP\Log\BeforeMessageLoggedEvent; use Psr\Container\ContainerInterface; class Application extends App implements IBootstrap { @@ -36,6 +38,7 @@ public function __construct(array $urlParams = []) { } public function register(IRegistrationContext $context): void { + $context->registerEventListener(BeforeMessageLoggedEvent::class, LogListener::class); $context->registerService(Formatter::class, function (ContainerInterface $c) { return new Formatter(\OC::$SERVERROOT); }); diff --git a/lib/Listener/LogListener.php b/lib/Listener/LogListener.php new file mode 100644 index 00000000..6017082b --- /dev/null +++ b/lib/Listener/LogListener.php @@ -0,0 +1,61 @@ + + * + * @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 OCA\LogReader\Listener; + +use OC\SystemConfig; +use OCA\LogReader\Log\Console; +use OCA\LogReader\Log\Formatter; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\Log\BeforeMessageLoggedEvent; +use Symfony\Component\Console\Terminal; + +class LogListener implements IEventListener { + private ?Console $console; + + public function __construct(Formatter $formatter, SystemConfig $config) { + if (defined('OC_CONSOLE') && \OC_CONSOLE) { + $level = getenv('OCC_LOG'); + if ($level) { + $terminal = new Terminal(); + $this->console = new Console($formatter, $config, $level, $terminal->getWidth()); + } else { + $this->console = null; + } + } else { + $this->console = null; + } + } + + + public function handle(Event $event): void { + if (!$event instanceof BeforeMessageLoggedEvent) { + return; + } + + if ($this->console) { + $this->console->log($event->getLevel(), $event->getApp(), $event->getMessage()); + } + } +} diff --git a/lib/Log/Console.php b/lib/Log/Console.php new file mode 100644 index 00000000..7e6a544c --- /dev/null +++ b/lib/Log/Console.php @@ -0,0 +1,87 @@ + + * + * @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 OCA\LogReader\Log; + +use OC\Log\LogDetails; +use OC\SystemConfig; +use OCA\LogReader\Command\Tail; + +/** + * Utility to write log messages to the console as they are emitted + */ +class Console extends LogDetails { + private int $level; + private int $terminalWidth; + private Formatter $formatter; + + public function __construct(Formatter $formatter, SystemConfig $config, string $level, int $terminalWidth) { + parent::__construct($config); + $this->formatter = $formatter; + $this->level = self::parseLogLevel($level); + $this->terminalWidth = $terminalWidth; + } + + public function log(int $level, string $app, array $entry) { + if ($level >= $this->level) { + $messageWidth = $this->terminalWidth - 8 - 18 - 6; + + $entry = $this->logDetails($app, $entry, $level); + + $lines = explode("\n", $this->formatter->formatMessage($entry, $messageWidth)); + $lines[0] = str_pad(Tail::LEVELS[$level], 8) . ' ' . + str_pad(wordwrap($app, 18), 18) . ' ' . + str_pad($lines[0], $messageWidth); + + for ($i = 1; $i < count($lines); $i++) { + $lines[$i] = str_repeat(' ', 8 + 18 + 2) . $lines[$i]; + } + + foreach ($lines as $line) { + fwrite(STDERR, $line . "\n"); + } + fwrite(STDERR, "\n"); + } + } + + private static function parseLogLevel(string $level): int { + if (is_numeric($level)) { + return (int)$level; + } + + switch (strtoupper($level)) { + case "DEBUG": + return 0; + case "INFO": + return 1; + case "WARN": + return 2; + case "ERROR": + return 3; + case "FATAL": + return 4; + default: + throw new \Exception("Unknown log level $level"); + } + } +} diff --git a/tests/stubs/stub.phpstub b/tests/stubs/stub.phpstub index f57424fa..5a7d0120 100644 --- a/tests/stubs/stub.phpstub +++ b/tests/stubs/stub.phpstub @@ -32,3 +32,18 @@ namespace OC\Core\Command { class InterruptedException extends \Exception { } } + +namespace OC { + class SystemConfig { + } +} + +namespace OC\Log { + use OC\SystemConfig; + class LogDetails { + public function __construct(SystemConfig $config) { + } + public function logDetails(string $app, $message, int $level): array { + } + } +}