Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow to show logs of all logging types by proxying messages #1041

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion css/logreader-main.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/logreader-main.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/logreader-main.mjs.map

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions lib/BackgroundJobs/Rotate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2018 Arthur Schiwon <[email protected]>
*
* @author Arthur Schiwon <[email protected]>
* @author Christoph Wurst <[email protected]>
* @author Roeland Jago Douma <[email protected]>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\LogReader\BackgroundJobs;

use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
use OCP\Files\IAppData;
use OCP\Files\NotFoundException;
use OCP\IConfig;

class Rotate extends TimedJob {

public function __construct(

Check warning on line 38 in lib/BackgroundJobs/Rotate.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJobs/Rotate.php#L38

Added line #L38 was not covered by tests
ITimeFactory $time,
private IConfig $config,
private IAppData $appData,
) {
parent::__construct($time);

Check warning on line 43 in lib/BackgroundJobs/Rotate.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJobs/Rotate.php#L43

Added line #L43 was not covered by tests

$this->setInterval(60 * 60 * 3);

Check warning on line 45 in lib/BackgroundJobs/Rotate.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJobs/Rotate.php#L45

Added line #L45 was not covered by tests
}

protected function run($argument): void {

Check warning on line 48 in lib/BackgroundJobs/Rotate.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJobs/Rotate.php#L48

Added line #L48 was not covered by tests
try {
$folder = $this->appData->getFolder('logreader');
$file = $folder->getFile('nextcloud.log');
} catch (NotFoundException $e) {

Check warning on line 52 in lib/BackgroundJobs/Rotate.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJobs/Rotate.php#L50-L52

Added lines #L50 - L52 were not covered by tests
// Nothing to do if there is no log
return;

Check warning on line 54 in lib/BackgroundJobs/Rotate.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJobs/Rotate.php#L54

Added line #L54 was not covered by tests
}

$maxSize = $this->config->getSystemValue('log_rotate_size', 100 * 1024 * 1024);
if ($file->getSize() < $maxSize) {

Check warning on line 58 in lib/BackgroundJobs/Rotate.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJobs/Rotate.php#L57-L58

Added lines #L57 - L58 were not covered by tests
// nothing to do
return;

Check warning on line 60 in lib/BackgroundJobs/Rotate.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJobs/Rotate.php#L60

Added line #L60 was not covered by tests
}

if ($folder->fileExists('nextcloud.1.log')) {
$rotatedFile = $folder->getFile('nextcloud.1.log');

Check warning on line 64 in lib/BackgroundJobs/Rotate.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJobs/Rotate.php#L63-L64

Added lines #L63 - L64 were not covered by tests
} else {
$rotatedFile = $folder->newFile('nextcloud.1.log');

Check warning on line 66 in lib/BackgroundJobs/Rotate.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJobs/Rotate.php#L66

Added line #L66 was not covered by tests
}
$rotatedFile->putContent($file->getContent());
$file->putContent('');

Check warning on line 69 in lib/BackgroundJobs/Rotate.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJobs/Rotate.php#L68-L69

Added lines #L68 - L69 were not covered by tests
}
}
15 changes: 0 additions & 15 deletions lib/Controller/LogController.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
use OCA\LogReader\Log\SearchFilter;
use OCA\LogReader\Service\SettingsService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use Psr\Log\LoggerInterface;
Expand Down Expand Up @@ -55,13 +54,6 @@ public function __construct($appName,
* @return JSONResponse
*/
public function get($query = '', $count = 50, $offset = 0): JSONResponse {
$logType = $this->settingsService->getLoggingType();
// we only support web access when `log_type` is set to `file` (the default)
if ($logType !== 'file') {
$this->logger->debug('File-based logging must be enabled to access logs from the Web UI.');
return new JSONResponse([], Http::STATUS_FAILED_DEPENDENCY);
}

$iterator = $this->logIteratorFactory->getLogIterator($this->settingsService->getShownLevels());

if ($query !== '') {
Expand Down Expand Up @@ -100,13 +92,6 @@ private function getLastItem() {
* request.
*/
public function poll(string $lastReqId): JSONResponse {
$logType = $this->settingsService->getLoggingType();
// we only support web access when `log_type` is set to `file` (the default)
if ($logType !== 'file') {
$this->logger->debug('File-based logging must be enabled to access logs from the Web UI.');
return new JSONResponse([], Http::STATUS_FAILED_DEPENDENCY);
}

$lastItem = $this->getLastItem();
if ($lastItem === null || $lastItem['reqId'] === $lastReqId) {
return new JSONResponse([]);
Expand Down
20 changes: 16 additions & 4 deletions lib/Listener/LogListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use OC\SystemConfig;
use OCA\LogReader\Log\Console;
use OCA\LogReader\Log\Formatter;
use OCA\LogReader\Log\LogProxy;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Log\BeforeMessageLoggedEvent;
Expand All @@ -35,9 +36,14 @@
* @template-implements IEventListener<BeforeMessageLoggedEvent>
*/
class LogListener implements IEventListener {
private ?Console $console;
private ?Console $console = null;
private ?LogProxy $proxy = null;

public function __construct(Formatter $formatter, SystemConfig $config) {
public function __construct(
Formatter $formatter,
SystemConfig $config,
LogProxy $proxy,
) {
if (defined('OC_CONSOLE') && \OC_CONSOLE) {
$level = getenv('OCC_LOG');
if ($level) {
Expand All @@ -46,8 +52,11 @@
} else {
$this->console = null;
}
} else {
$this->console = null;
}

// Only create a proxy file if log type is not file
if ($config->getValue('log_type', 'file') !== 'file') {
$this->proxy = $proxy;

Check warning on line 59 in lib/Listener/LogListener.php

View check run for this annotation

Codecov / codecov/patch

lib/Listener/LogListener.php#L59

Added line #L59 was not covered by tests
}
}

Expand All @@ -60,5 +69,8 @@
if ($this->console) {
$this->console->log($event->getLevel(), $event->getApp(), $event->getMessage());
}
if ($this->proxy) {
$this->proxy->log($event->getLevel(), $event->getApp(), $event->getMessage());

Check warning on line 73 in lib/Listener/LogListener.php

View check run for this annotation

Codecov / codecov/patch

lib/Listener/LogListener.php#L73

Added line #L73 was not covered by tests
}
}
}
53 changes: 38 additions & 15 deletions lib/Log/LogIteratorFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,19 @@

namespace OCA\LogReader\Log;

use OCP\Files\IAppData;
use OCP\Files\NotFoundException;
use OCP\IConfig;
use OCP\Log\IFileBased;
use OCP\Log\ILogFactory;

class LogIteratorFactory {
private $config;
private $logFactory;

public function __construct(IConfig $config, ILogFactory $logFactory) {
$this->config = $config;
$this->logFactory = $logFactory;
public function __construct(

Check warning on line 34 in lib/Log/LogIteratorFactory.php

View check run for this annotation

Codecov / codecov/patch

lib/Log/LogIteratorFactory.php#L34

Added line #L34 was not covered by tests
private IConfig $config,
private ILogFactory $logFactory,
private IAppData $appData,
) {
}

/**
Expand All @@ -44,18 +46,39 @@
public function getLogIterator(array $levels) {
$dateFormat = $this->config->getSystemValue('logdateformat', \DateTime::ATOM);
$timezone = $this->config->getSystemValue('logtimezone', 'UTC');
$log = $this->logFactory->get('file');
if ($log instanceof IFileBased) {
$handle = fopen($log->getLogFilePath(), 'rb');
if ($handle) {
$iterator = new LogIterator($handle, $dateFormat, $timezone);
return new \CallbackFilterIterator($iterator, function ($logItem) use ($levels) {
return $logItem && in_array($logItem['level'], $levels);
});

if ($this->config->getSystemValue('log_type', 'file') !== 'file') {

Check warning on line 50 in lib/Log/LogIteratorFactory.php

View check run for this annotation

Codecov / codecov/patch

lib/Log/LogIteratorFactory.php#L50

Added line #L50 was not covered by tests
try {
$folder = $this->appData->getFolder('logreader');
} catch (NotFoundException $e) {
$folder = $this->appData->newFolder('logreader');

Check warning on line 54 in lib/Log/LogIteratorFactory.php

View check run for this annotation

Codecov / codecov/patch

lib/Log/LogIteratorFactory.php#L52-L54

Added lines #L52 - L54 were not covered by tests
}

try {
$logfile = $folder->getFile('nextcloud.log');
} catch (NotFoundException $e) {
$logfile = $folder->newFile('nextcloud.log');

Check warning on line 60 in lib/Log/LogIteratorFactory.php

View check run for this annotation

Codecov / codecov/patch

lib/Log/LogIteratorFactory.php#L58-L60

Added lines #L58 - L60 were not covered by tests
}

$handle = $logfile->read();
if (!$handle) {
throw new \Exception('Error while opening internal logfile');

Check warning on line 65 in lib/Log/LogIteratorFactory.php

View check run for this annotation

Codecov / codecov/patch

lib/Log/LogIteratorFactory.php#L63-L65

Added lines #L63 - L65 were not covered by tests
}
} else {
$logfile = $this->logFactory->get('file');
if ($logfile instanceof IFileBased) {
$handle = fopen($logfile->getLogFilePath(), 'rb');
if (!$handle) {
throw new \Exception("Error while opening " . $logfile->getLogFilePath());

Check warning on line 72 in lib/Log/LogIteratorFactory.php

View check run for this annotation

Codecov / codecov/patch

lib/Log/LogIteratorFactory.php#L68-L72

Added lines #L68 - L72 were not covered by tests
}
} else {
throw new \Exception("Error while opening " . $log->getLogFilePath());
throw new \Exception('Can\'t find log class');

Check warning on line 75 in lib/Log/LogIteratorFactory.php

View check run for this annotation

Codecov / codecov/patch

lib/Log/LogIteratorFactory.php#L75

Added line #L75 was not covered by tests
}
}
throw new \Exception('Can\'t find log class');

$iterator = new LogIterator($handle, $dateFormat, $timezone);
return new \CallbackFilterIterator($iterator, function ($logItem) use ($levels) {
return $logItem && in_array($logItem['level'], $levels);
});

Check warning on line 82 in lib/Log/LogIteratorFactory.php

View check run for this annotation

Codecov / codecov/patch

lib/Log/LogIteratorFactory.php#L79-L82

Added lines #L79 - L82 were not covered by tests
}
}
82 changes: 82 additions & 0 deletions lib/Log/LogProxy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Ferdinand Thiessen <[email protected]>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\LogReader\Log;

use OC\Log\LogDetails;
use OC\SystemConfig;
use OCP\Files\IAppData;
use OCP\Files\NotFoundException;
use OCP\Files\SimpleFS\ISimpleFile;

/**
* Create a logfile even for syslog etc loggers
*/
class LogProxy extends LogDetails {
private ISimpleFile $logfile;

public function __construct(IAppData $appData, private SystemConfig $config) {
parent::__construct($config);

try {
$folder = $appData->getFolder('logreader');
} catch (NotFoundException $e) {
$folder = $appData->newFolder('logreader');
}

try {
$this->logfile = $folder->getFile('nextcloud.log');
} catch (NotFoundException $e) {
$this->logfile = $folder->newFile('nextcloud.log');
}
}

public function log(int $level, string $app, array $entry) {
if ($level >= $this->config->getValue('loglevel', 1)) {
$hasBacktrace = isset($entry['exception']);
$logBacktrace = $this->config->getValue('log.backtrace', false);
if (!$hasBacktrace && $logBacktrace) {
$entry['backtrace'] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
}

$entry = $this->logDetailsAsJSON($app, $entry, $level);

$handle = $this->logfile->read();
if ($handle) {
$content = stream_get_contents($handle);
fclose($handle);
}

$handle = $this->logfile->write();
if ($handle) {
if (isset($content) && $content !== false) {
fwrite($handle, $content);
}
fwrite($handle, $entry."\n");
fclose($handle);
} else {
throw new \Error('Could not open internal log file.');
}
}
}
}
1 change: 0 additions & 1 deletion lib/Service/SettingsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ public function getAppSettings(): array {
Constants::CONFIG_KEY_DATETIMEFORMAT => $this->getDateTimeFormat(),
Constants::CONFIG_KEY_RELATIVEDATES => $this->getRelativeDates(),
Constants::CONFIG_KEY_LIVELOG => $this->getLiveLog(),
'enabled' => $this->getLoggingType() === 'file',
];
}

Expand Down
4 changes: 3 additions & 1 deletion psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
<directory name="vendor/nextcloud/ocp"/>
</extraFiles>
<stubs>
<file name="tests/stubs/stub.phpstub" preloadClasses="true"/>
<file name="tests/stubs/oc_core_command.php" preloadClasses="true"/>
<file name="tests/stubs/oc_log_LogDetails.php"/>
<file name="tests/stubs/oc_SystemConfig.php" preloadClasses="true"/>
</stubs>
<issueHandlers>
<UndefinedClass>
Expand Down
31 changes: 7 additions & 24 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
</NcButton>
</div>
<!-- Show information / warning message -->
<NcNoteCard v-if="settingsStore.localFile" type="info" class="info-note">
<NcNoteCard v-if="!settingsStore.isServerLogShown" type="info" class="info-note">
<div class="info-note__content">
<p>{{ t('logreader', 'Currently the log file {file} is shown', { file: settingsStore.localFileName }) }}</p>
<NcButton type="secondary" @click="onShowServerLog">
Expand All @@ -30,17 +30,13 @@
<p>{{ t('logreader', 'Live view is disabled') }}</p>
</NcNoteCard>
<!-- Show the log file table -->
<LogTable v-if="settingsStore.enabled" :rows="entries" />
<NcEmptyContent v-else :name="t('logreader', 'No log file')">
<LogTable v-if="hasEntries" :rows="entries" />
<NcEmptyContent v-else
:name="t('logreader', 'No log entries')"
:description="t('logreader', 'The log is currently empty. Try to select a different logging level.')">
<template #icon>
<IconFormatList :size="20" />
</template>
<template #description>
{{ t('logreader', 'File-based logging must be enabled to access logs from the Web UI.') }}
<br>
<!-- eslint-disable-next-line vue/no-v-html -->
<span v-html="noLogDescription" />
</template>
</NcEmptyContent>
<!-- App settings dialog will be mounted on page body -->
<AppSettingsDialog :open.sync="areSettingsShown" />
Expand Down Expand Up @@ -74,6 +70,8 @@ const loggingStore = useLogStore()

const entries = computed(() => loggingStore.entries)

const hasEntries = computed(() => loggingStore.allEntries.length > 0)

const onShowServerLog = () => {
settingsStore.localFile = undefined
// remove local entries
Expand All @@ -99,21 +97,6 @@ onMounted(() => {
onUnmounted(() => {
loggingStore.stopPolling()
})

/** Translated description what to check in case no log can be loaded */
const noLogDescription = t(
'logreader',
'If you feel this is an error, please verify {setting} in your {config} and check the Nextcloud Administration Manual.',
{
setting: '<code>log_type</code>',
config: '<code>config.php</code>',
},
0,
{
sanitize: false,
escape: false,
},
)
</script>

<style lang="scss" scoped>
Expand Down
Loading
Loading