Skip to content

Commit 55f07a4

Browse files
authored
Merge pull request #32985 from nextcloud/backport/32242/stable23
[stable23] Fix logging data context to file
2 parents 314ed26 + 54b0b53 commit 55f07a4

4 files changed

Lines changed: 75 additions & 24 deletions

File tree

lib/private/Log.php

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* @author Olivier Paroz <github@oparoz.com>
1616
* @author Robin Appelman <robin@icewind.nl>
1717
* @author Roeland Jago Douma <roeland@famdouma.nl>
18+
* @author Thomas Citharel <nextcloud@tcit.fr>
1819
* @author Thomas Müller <thomas.mueller@tmit.eu>
1920
* @author Victor Dubiniuk <dubiniuk@owncloud.com>
2021
*
@@ -207,11 +208,11 @@ public function log(int $level, string $message, array $context = []) {
207208
array_walk($context, [$this->normalizer, 'format']);
208209

209210
$app = $context['app'] ?? 'no app in context';
210-
$message = $this->interpolateMessage($context, $message);
211+
$entry = $this->interpolateMessage($context, $message);
211212

212213
try {
213214
if ($level >= $minLevel) {
214-
$this->writeLog($app, $message, $level);
215+
$this->writeLog($app, $entry, $level);
215216

216217
if ($this->crashReporters !== null) {
217218
$messageContext = array_merge(
@@ -220,11 +221,11 @@ public function log(int $level, string $message, array $context = []) {
220221
'level' => $level
221222
]
222223
);
223-
$this->crashReporters->delegateMessage($message, $messageContext);
224+
$this->crashReporters->delegateMessage($entry['message'], $messageContext);
224225
}
225226
} else {
226227
if ($this->crashReporters !== null) {
227-
$this->crashReporters->delegateBreadcrumb($message, 'log', $context);
228+
$this->crashReporters->delegateBreadcrumb($entry['message'], 'log', $context);
228229
}
229230
}
230231
} catch (\Throwable $e) {
@@ -309,8 +310,11 @@ public function logException(\Throwable $exception, array $context = []) {
309310
$level = $context['level'] ?? ILogger::ERROR;
310311

311312
$serializer = new ExceptionSerializer($this->config);
312-
$data = $serializer->serializeException($exception);
313-
$data['CustomMessage'] = $this->interpolateMessage($context, $context['message'] ?? '--');
313+
$data = $context;
314+
unset($data['app']);
315+
unset($data['level']);
316+
$data = array_merge($serializer->serializeException($exception), $data);
317+
$data = $this->interpolateMessage($data, $context['message'] ?? '--', 'CustomMessage');
314318

315319
$minLevel = $this->getLogLevel($context);
316320

@@ -375,16 +379,20 @@ public function getLogPath():string {
375379
/**
376380
* Interpolate $message as defined in PSR-3
377381
*
378-
* @param array $context
379-
* @param string $message
380-
*
381-
* @return string
382+
* Returns an array containing the context without the interpolated
383+
* parameters placeholders and the message as the 'message' - or
384+
* user-defined - key.
382385
*/
383-
private function interpolateMessage(array $context, string $message): string {
386+
private function interpolateMessage(array $context, string $message, string $messageKey = 'message'): array {
384387
$replace = [];
388+
$usedContextKeys = [];
385389
foreach ($context as $key => $val) {
386-
$replace['{' . $key . '}'] = $val;
390+
$fullKey = '{' . $key . '}';
391+
$replace[$fullKey] = $val;
392+
if (strpos($message, $fullKey) !== false) {
393+
$usedContextKeys[$key] = true;
394+
}
387395
}
388-
return strtr($message, $replace);
396+
return array_merge(array_diff_key($context, $usedContextKeys), [$messageKey => strtr($message, $replace)]);
389397
}
390398
}

lib/private/Log/LogDetails.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
66
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
77
* @author Julius Härtl <jus@bitgrid.net>
8+
* @author Thomas Citharel <nextcloud@tcit.fr>
89
*
910
* @license GNU AGPL version 3 or any later version
1011
*
@@ -90,8 +91,9 @@ public function logDetails(string $app, $message, int $level): array {
9091
$entry['exception'] = $message;
9192
$entry['message'] = $message['CustomMessage'] !== '--' ? $message['CustomMessage'] : $message['Message'];
9293
} else {
93-
$entry['data'] = $message;
9494
$entry['message'] = $message['message'] ?? '(no message provided)';
95+
unset($message['message']);
96+
$entry['data'] = $message;
9597
}
9698
}
9799

tests/lib/Log/FileTest.php

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22
/**
3+
*
4+
* @author Thomas Citharel <nextcloud@tcit.fr>
35
*
46
* This library is free software; you can redistribute it and/or
57
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
@@ -18,6 +20,7 @@
1820
namespace Test\Log;
1921

2022
use OC\Log\File;
23+
use OCP\IConfig;
2124
use OCP\ILogger;
2225
use Test\TestCase;
2326

@@ -36,7 +39,7 @@ protected function setUp(): void {
3639
$config = \OC::$server->getSystemConfig();
3740
$this->restore_logfile = $config->getValue("logfile");
3841
$this->restore_logdateformat = $config->getValue('logdateformat');
39-
42+
4043
$config->setValue("logfile", $config->getValue('datadirectory') . "/logtest.log");
4144
$this->logFile = new File($config->getValue('datadirectory') . '/logtest.log', '', $config);
4245
}
@@ -55,7 +58,28 @@ protected function tearDown(): void {
5558
$this->logFile = new File($this->restore_logfile, '', $config);
5659
parent::tearDown();
5760
}
58-
61+
62+
public function testLogging() {
63+
$config = \OC::$server->get(IConfig::class);
64+
# delete old logfile
65+
unlink($config->getSystemValue('logfile'));
66+
67+
# set format & write log line
68+
$config->setSystemValue('logdateformat', 'u');
69+
$this->logFile->write('code', ['something' => 'extra', 'message' => 'Testing logging'], ILogger::ERROR);
70+
71+
# read log line
72+
$handle = @fopen($config->getSystemValue('logfile'), 'r');
73+
$line = fread($handle, 1000);
74+
fclose($handle);
75+
76+
# check log has data content
77+
$values = (array) json_decode($line, true);
78+
$this->assertArrayNotHasKey('message', $values['data']);
79+
$this->assertEquals('extra', $values['data']['something']);
80+
$this->assertEquals('Testing logging', $values['message']);
81+
}
82+
5983
public function testMicrosecondsLogTimestamp() {
6084
$config = \OC::$server->getConfig();
6185
# delete old logfile
@@ -69,7 +93,7 @@ public function testMicrosecondsLogTimestamp() {
6993
$handle = @fopen($config->getSystemValue('logfile'), 'r');
7094
$line = fread($handle, 1000);
7195
fclose($handle);
72-
96+
7397
# check timestamp has microseconds part
7498
$values = (array) json_decode($line);
7599
$microseconds = $values['time'];

tests/lib/LoggerTest.php

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<?php
22
/**
33
* Copyright (c) 2014 Thomas Müller <thomas.mueller@tmit.eu>
4+
*
5+
* @author Thomas Citharel <nextcloud@tcit.fr>
6+
*
47
* This file is licensed under the Affero General Public License version 3 or
58
* later.
69
* See the COPYING-README file.
@@ -9,18 +12,21 @@
912
namespace Test;
1013

1114
use OC\Log;
15+
use OC\SystemConfig;
1216
use OCP\ILogger;
1317
use OCP\Log\IWriter;
18+
use OCP\Support\CrashReport\IRegistry;
19+
use PHPUnit\Framework\MockObject\MockObject;
1420

1521
class LoggerTest extends TestCase implements IWriter {
1622

17-
/** @var \OC\SystemConfig|\PHPUnit\Framework\MockObject\MockObject */
23+
/** @var SystemConfig|MockObject */
1824
private $config;
1925

20-
/** @var \OCP\Support\CrashReport\IRegistry|\PHPUnit\Framework\MockObject\MockObject */
26+
/** @var IRegistry|MockObject */
2127
private $registry;
2228

23-
/** @var \OCP\ILogger */
29+
/** @var ILogger */
2430
private $logger;
2531

2632
/** @var array */
@@ -30,8 +36,8 @@ protected function setUp(): void {
3036
parent::setUp();
3137

3238
$this->logs = [];
33-
$this->config = $this->createMock(\OC\SystemConfig::class);
34-
$this->registry = $this->createMock(\OCP\Support\CrashReport\IRegistry::class);
39+
$this->config = $this->createMock(SystemConfig::class);
40+
$this->registry = $this->createMock(IRegistry::class);
3541
$this->logger = new Log($this, $this->config, null, $this->registry);
3642
}
3743

@@ -63,12 +69,23 @@ public function testAppCondition() {
6369
$this->assertEquals($expected, $this->getLogs());
6470
}
6571

66-
private function getLogs() {
72+
public function testLoggingWithDataArray(): void {
73+
$writerMock = $this->createMock(IWriter::class);
74+
$logFile = new Log($writerMock, $this->config);
75+
$writerMock->expects($this->once())->method('write')->with('no app in context', ['something' => 'extra', 'message' => 'Testing logging with john']);
76+
$logFile->error('Testing logging with {user}', ['something' => 'extra', 'user' => 'john']);
77+
}
78+
79+
private function getLogs(): array {
6780
return $this->logs;
6881
}
6982

7083
public function write(string $app, $message, int $level) {
71-
$this->logs[] = "$level $message";
84+
$textMessage = $message;
85+
if (is_array($message)) {
86+
$textMessage = $message['message'];
87+
}
88+
$this->logs[] = $level . " " . $textMessage;
7289
}
7390

7491
public function userAndPasswordData(): array {

0 commit comments

Comments
 (0)