Skip to content

Commit 9375927

Browse files
committed
feat: Allow to show all log types by adding an internal log file for non-file based loggers
Also no longer provide the "enabled" setting as now all log types are supported Signed-off-by: Ferdinand Thiessen <[email protected]>
1 parent 29d0240 commit 9375927

File tree

14 files changed

+342
-104
lines changed

14 files changed

+342
-104
lines changed

lib/Controller/LogController.php

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
use OCA\LogReader\Log\SearchFilter;
2727
use OCA\LogReader\Service\SettingsService;
2828
use OCP\AppFramework\Controller;
29-
use OCP\AppFramework\Http;
3029
use OCP\AppFramework\Http\JSONResponse;
3130
use OCP\IRequest;
3231
use Psr\Log\LoggerInterface;
@@ -55,13 +54,6 @@ public function __construct($appName,
5554
* @return JSONResponse
5655
*/
5756
public function get($query = '', $count = 50, $offset = 0): JSONResponse {
58-
$logType = $this->settingsService->getLoggingType();
59-
// we only support web access when `log_type` is set to `file` (the default)
60-
if ($logType !== 'file') {
61-
$this->logger->debug('File-based logging must be enabled to access logs from the Web UI.');
62-
return new JSONResponse([], Http::STATUS_FAILED_DEPENDENCY);
63-
}
64-
6557
$iterator = $this->logIteratorFactory->getLogIterator($this->settingsService->getShownLevels());
6658

6759
if ($query !== '') {
@@ -100,13 +92,6 @@ private function getLastItem() {
10092
* request.
10193
*/
10294
public function poll(string $lastReqId): JSONResponse {
103-
$logType = $this->settingsService->getLoggingType();
104-
// we only support web access when `log_type` is set to `file` (the default)
105-
if ($logType !== 'file') {
106-
$this->logger->debug('File-based logging must be enabled to access logs from the Web UI.');
107-
return new JSONResponse([], Http::STATUS_FAILED_DEPENDENCY);
108-
}
109-
11095
$lastItem = $this->getLastItem();
11196
if ($lastItem === null || $lastItem['reqId'] === $lastReqId) {
11297
return new JSONResponse([]);

lib/Listener/LogListener.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use OC\SystemConfig;
2727
use OCA\LogReader\Log\Console;
2828
use OCA\LogReader\Log\Formatter;
29+
use OCA\LogReader\Log\LogProxy;
2930
use OCP\EventDispatcher\Event;
3031
use OCP\EventDispatcher\IEventListener;
3132
use OCP\Log\BeforeMessageLoggedEvent;
@@ -35,9 +36,14 @@
3536
* @template-implements IEventListener<BeforeMessageLoggedEvent>
3637
*/
3738
class LogListener implements IEventListener {
38-
private ?Console $console;
39+
private ?Console $console = null;
40+
private ?LogProxy $proxy = null;
3941

40-
public function __construct(Formatter $formatter, SystemConfig $config) {
42+
public function __construct(
43+
Formatter $formatter,
44+
SystemConfig $config,
45+
LogProxy $proxy,
46+
) {
4147
if (defined('OC_CONSOLE') && \OC_CONSOLE) {
4248
$level = getenv('OCC_LOG');
4349
if ($level) {
@@ -46,8 +52,11 @@ public function __construct(Formatter $formatter, SystemConfig $config) {
4652
} else {
4753
$this->console = null;
4854
}
49-
} else {
50-
$this->console = null;
55+
}
56+
57+
// Only create a proxy file if log type is not file
58+
if ($config->getValue('log_type', 'file') !== 'file') {
59+
$this->proxy = $proxy;
5160
}
5261
}
5362

@@ -60,5 +69,8 @@ public function handle(Event $event): void {
6069
if ($this->console) {
6170
$this->console->log($event->getLevel(), $event->getApp(), $event->getMessage());
6271
}
72+
if ($this->proxy) {
73+
$this->proxy->log($event->getLevel(), $event->getApp(), $event->getMessage());
74+
}
6375
}
6476
}

lib/Log/LogIteratorFactory.php

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,19 @@
2323

2424
namespace OCA\LogReader\Log;
2525

26+
use OCP\Files\IAppData;
27+
use OCP\Files\NotFoundException;
2628
use OCP\IConfig;
2729
use OCP\Log\IFileBased;
2830
use OCP\Log\ILogFactory;
2931

3032
class LogIteratorFactory {
31-
private $config;
32-
private $logFactory;
3333

34-
public function __construct(IConfig $config, ILogFactory $logFactory) {
35-
$this->config = $config;
36-
$this->logFactory = $logFactory;
34+
public function __construct(
35+
private IConfig $config,
36+
private ILogFactory $logFactory,
37+
private IAppData $appData,
38+
) {
3739
}
3840

3941
/**
@@ -44,18 +46,39 @@ public function __construct(IConfig $config, ILogFactory $logFactory) {
4446
public function getLogIterator(array $levels) {
4547
$dateFormat = $this->config->getSystemValue('logdateformat', \DateTime::ATOM);
4648
$timezone = $this->config->getSystemValue('logtimezone', 'UTC');
47-
$log = $this->logFactory->get('file');
48-
if ($log instanceof IFileBased) {
49-
$handle = fopen($log->getLogFilePath(), 'rb');
50-
if ($handle) {
51-
$iterator = new LogIterator($handle, $dateFormat, $timezone);
52-
return new \CallbackFilterIterator($iterator, function ($logItem) use ($levels) {
53-
return $logItem && in_array($logItem['level'], $levels);
54-
});
49+
50+
if ($this->config->getSystemValue('log_type', 'file') !== 'file') {
51+
try {
52+
$folder = $this->appData->getFolder('logreader');
53+
} catch (NotFoundException $e) {
54+
$folder = $this->appData->newFolder('logreader');
55+
}
56+
57+
try {
58+
$logfile = $folder->getFile('nextcloud.log');
59+
} catch (NotFoundException $e) {
60+
$logfile = $folder->newFile('nextcloud.log');
61+
}
62+
63+
$handle = $logfile->read();
64+
if (!$handle) {
65+
throw new \Exception('Error while opening internal logfile');
66+
}
67+
} else {
68+
$logfile = $this->logFactory->get('file');
69+
if ($logfile instanceof IFileBased) {
70+
$handle = fopen($logfile->getLogFilePath(), 'rb');
71+
if (!$handle) {
72+
throw new \Exception("Error while opening " . $logfile->getLogFilePath());
73+
}
5574
} else {
56-
throw new \Exception("Error while opening " . $log->getLogFilePath());
75+
throw new \Exception('Can\'t find log class');
5776
}
5877
}
59-
throw new \Exception('Can\'t find log class');
78+
79+
$iterator = new LogIterator($handle, $dateFormat, $timezone);
80+
return new \CallbackFilterIterator($iterator, function ($logItem) use ($levels) {
81+
return $logItem && in_array($logItem['level'], $levels);
82+
});
6083
}
6184
}

lib/Log/LogProxy.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* @copyright Copyright (c) 2023 Ferdinand Thiessen <[email protected]>
6+
*
7+
* @license GNU AGPL version 3 or any later version
8+
*
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Affero General Public License as
11+
* published by the Free Software Foundation, either version 3 of the
12+
* License, or (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU Affero General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Affero General Public License
20+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
*
22+
*/
23+
24+
namespace OCA\LogReader\Log;
25+
26+
use OC\Log\LogDetails;
27+
use OC\SystemConfig;
28+
use OCP\Files\IAppData;
29+
use OCP\Files\NotFoundException;
30+
use OCP\Files\SimpleFS\ISimpleFile;
31+
32+
/**
33+
* Create a logfile even for syslog etc loggers
34+
*/
35+
class LogProxy extends LogDetails {
36+
private ISimpleFile $logfile;
37+
38+
public function __construct(IAppData $appData, private SystemConfig $config) {
39+
parent::__construct($config);
40+
41+
try {
42+
$folder = $appData->getFolder('logreader');
43+
} catch (NotFoundException $e) {
44+
$folder = $appData->newFolder('logreader');
45+
}
46+
47+
try {
48+
$this->logfile = $folder->getFile('nextcloud.log');
49+
} catch (NotFoundException $e) {
50+
$this->logfile = $folder->newFile('nextcloud.log');
51+
}
52+
}
53+
54+
public function log(int $level, string $app, array $entry) {
55+
if ($level >= $this->config->getValue('loglevel', 1)) {
56+
$hasBacktrace = isset($entry['exception']);
57+
$logBacktrace = $this->config->getValue('log.backtrace', false);
58+
if (!$hasBacktrace && $logBacktrace) {
59+
$entry['backtrace'] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
60+
}
61+
62+
$entry = $this->logDetailsAsJSON($app, $entry, $level);
63+
64+
$handle = $this->logfile->read();
65+
if ($handle) {
66+
$content = stream_get_contents($handle);
67+
fclose($handle);
68+
}
69+
70+
$handle = $this->logfile->write();
71+
if ($handle) {
72+
if (isset($content) && $content !== false) {
73+
fwrite($handle, $content);
74+
}
75+
fwrite($handle, $entry."\n");
76+
fclose($handle);
77+
} else {
78+
throw new \Error('Could not open internal log file.');
79+
}
80+
}
81+
}
82+
}

lib/Service/SettingsService.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ public function getAppSettings(): array {
6868
Constants::CONFIG_KEY_DATETIMEFORMAT => $this->getDateTimeFormat(),
6969
Constants::CONFIG_KEY_RELATIVEDATES => $this->getRelativeDates(),
7070
Constants::CONFIG_KEY_LIVELOG => $this->getLiveLog(),
71-
'enabled' => $this->getLoggingType() === 'file',
7271
];
7372
}
7473

src/App.vue

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
</NcButton>
1919
</div>
2020
<!-- Show information / warning message -->
21-
<NcNoteCard v-if="settingsStore.localFile" type="info" class="info-note">
21+
<NcNoteCard v-if="!settingsStore.isServerLogShown" type="info" class="info-note">
2222
<div class="info-note__content">
2323
<p>{{ t('logreader', 'Currently the log file {file} is shown', { file: settingsStore.localFileName }) }}</p>
2424
<NcButton type="secondary" @click="onShowServerLog">
@@ -30,17 +30,13 @@
3030
<p>{{ t('logreader', 'Live view is disabled') }}</p>
3131
</NcNoteCard>
3232
<!-- Show the log file table -->
33-
<LogTable v-if="settingsStore.enabled" :rows="entries" />
34-
<NcEmptyContent v-else :name="t('logreader', 'No log file')">
33+
<LogTable v-if="hasEntries" :rows="entries" />
34+
<NcEmptyContent v-else
35+
:name="t('logreader', 'No log entries')"
36+
:description="t('logreader', 'The log is currently empty. Try to select a different logging level.')">
3537
<template #icon>
3638
<IconFormatList :size="20" />
3739
</template>
38-
<template #description>
39-
{{ t('logreader', 'File-based logging must be enabled to access logs from the Web UI.') }}
40-
<br>
41-
<!-- eslint-disable-next-line vue/no-v-html -->
42-
<span v-html="noLogDescription" />
43-
</template>
4440
</NcEmptyContent>
4541
<!-- App settings dialog will be mounted on page body -->
4642
<AppSettingsDialog :open.sync="areSettingsShown" />
@@ -74,6 +70,8 @@ const loggingStore = useLogStore()
7470
7571
const entries = computed(() => loggingStore.entries)
7672
73+
const hasEntries = computed(() => loggingStore.allEntries.length > 0)
74+
7775
const onShowServerLog = () => {
7876
settingsStore.localFile = undefined
7977
// remove local entries
@@ -99,21 +97,6 @@ onMounted(() => {
9997
onUnmounted(() => {
10098
loggingStore.stopPolling()
10199
})
102-
103-
/** Translated description what to check in case no log can be loaded */
104-
const noLogDescription = t(
105-
'logreader',
106-
'If you feel this is an error, please verify {setting} in your {config} and check the Nextcloud Administration Manual.',
107-
{
108-
setting: '<code>log_type</code>',
109-
config: '<code>config.php</code>',
110-
},
111-
0,
112-
{
113-
sanitize: false,
114-
escape: false,
115-
},
116-
)
117100
</script>
118101

119102
<style lang="scss" scoped>

src/interfaces/IAppSettings.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,4 @@ export interface IAppSettings {
2323
* Wether the log should be polled
2424
*/
2525
liveLog: boolean
26-
/**
27-
* Wether backend is enable = logging is set to file
28-
*/
29-
enabled: boolean
3026
}

src/store/logging.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ describe('store:logging', () => {
5353
// Mock server settings
5454
mockInitialState({
5555
dateTimeFormat: 'local',
56-
enabled: true,
5756
liveLog: true,
5857
shownLevels: [2, 4],
5958
})

src/store/logging.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export const useLogStore = defineStore('logreader-logs', () => {
6565
*/
6666
async function loadMore(older = true) {
6767
// Nothing to do if server logging is disabled
68-
if (!_settings.isEnabled) return
68+
if (!_settings.isServerLogShown) return
6969

7070
// Only load any entries if there is no previous unfinished request
7171
if (!(_loading.value = !_loading.value)) return
@@ -117,7 +117,7 @@ export const useLogStore = defineStore('logreader-logs', () => {
117117
const doPolling = async () => {
118118
try {
119119
// Only poll if not using a local file
120-
if (_settings.isEnabled && query.value === '') {
120+
if (_settings.isServerLogShown && query.value === '') {
121121
const { data } = await pollLog({ lastReqId: allEntries.value[0]?.reqId || '' })
122122
allEntries.value.splice(0, 0, ...data.map(parseRawLogEntry))
123123
}
@@ -153,7 +153,7 @@ export const useLogStore = defineStore('logreader-logs', () => {
153153
query.value = search
154154

155155
// if query changed and server logging is enabled, request new entries
156-
if (search !== oldQuery && _settings.isEnabled) {
156+
if (search !== oldQuery && _settings.isServerLogShown) {
157157
_loading.value = true
158158

159159
try {

0 commit comments

Comments
 (0)