@@ -4,10 +4,16 @@ Logging
44
55.. sectionauthor :: Bernhard Posselt <dev@bernhard-posselt.com>
66
7- The logger is present by default in the container. The app that is logging is
8- set automatically. Nextcloud uses a :ref: `PSR3 < psr3 >` logger .
7+ Nextcloud uses a :ref: ` PSR-3 < psr3 >` logger (`` Psr\Log\LoggerInterface ``).
8+ The recommended way to use it is via :ref: `dependency injection < dependency-injection >` .
99
10- The logger can be used in the following way:
10+ Basic usage
11+ -----------
12+
13+ Inject ``Psr\Log\LoggerInterface `` into your class. When the logger is resolved
14+ from an app container, Nextcloud automatically wraps it so that every log
15+ message is attributed to your app -- you do **not ** need to pass an app name
16+ yourself:
1117
1218.. code-block :: php
1319
@@ -17,19 +23,21 @@ The logger can be used in the following way:
1723 use Psr\Log\LoggerInterface;
1824
1925 class AuthorService {
20- private LoggerInterface $logger;
21- private string $appName;
22-
23- public function __construct(LoggerInterface $logger, string $appName){
24- $this->logger = $logger;
25- $this->appName = $appName;
26+ public function __construct(
27+ private LoggerInterface $logger,
28+ ) {
2629 }
2730
28- public function log($message) {
29- $this->logger->error($message, ['extra_context' => 'my extra context'] );
31+ public function doSomething(): void {
32+ $this->logger->error('Something went wrong' );
3033 }
3134 }
3235
36+ .. note ::
37+ Behind the scenes, each app's DI container registers a ``ScopedPsrLogger ``
38+ that prepends ``['app' => '<your-app-id>'] `` to the context of every call.
39+ You do not need to set this manually.
40+
3341In cases where you can not :ref: `inject <dependency-injection >` a logger into a class, you can use
3442the ``\OCP\Log\logger `` function to acquire a logger instance. As a first argument you need to pass
3543the app ID.
@@ -40,7 +48,253 @@ the app ID.
4048
4149 use function OCP\Log\logger;
4250
43- logger('calendar')->warning('look, no dependency injection');
51+ logger('my_app')->warning('look, no dependency injection');
52+
53+
54+ Available log methods
55+ ---------------------
56+
57+ The logger exposes the full set of PSR-3 methods:
58+
59+ - ``emergency() `` -- System is unusable
60+ - ``alert() `` -- Action must be taken immediately
61+ - ``critical() `` -- Critical conditions
62+ - ``error() `` -- Runtime errors
63+ - ``warning() `` -- Exceptional occurrences that are not errors
64+ - ``notice() `` -- Normal but significant events
65+ - ``info() `` -- Interesting events (e.g. user logs in)
66+ - ``debug() `` -- Detailed debug information
67+
68+ Each method has the signature ``(string|\Stringable $message, array $context = []): void ``.
69+
70+
71+ Log level mapping
72+ -----------------
73+
74+ PSR-3 defines eight severity levels, but Nextcloud internally uses five numeric
75+ levels (set via the ``loglevel `` setting in ``config.php ``). The mapping is:
76+
77+ .. list-table ::
78+ :header-rows: 1
79+
80+ * - PSR-3 level
81+ - Nextcloud level
82+ - Numeric value
83+ * - ``emergency ``
84+ - FATAL
85+ - 4
86+ * - ``alert ``, ``critical ``, ``error ``
87+ - ERROR
88+ - 3
89+ * - ``warning ``
90+ - WARN
91+ - 2
92+ * - ``notice ``, ``info ``
93+ - INFO
94+ - 1
95+ * - ``debug ``
96+ - DEBUG
97+ - 0
98+
99+ A message is written to the log only when its numeric level is **≥ ** the
100+ configured ``loglevel `` (default: ``2 `` / WARN).
101+
102+
103+ Message interpolation
104+ ---------------------
105+
106+ Nextcloud supports PSR-3 message interpolation. Context values whose keys
107+ appear as ``{placeholder} `` tokens in the message string are substituted
108+ automatically:
109+
110+ .. code-block :: php
111+
112+ $this->logger->warning('User {user} failed to log in', [
113+ 'user' => $userId,
114+ ]);
115+
116+ This produces a log message like ``User jane failed to log in ``. The ``user ``
117+ key is consumed by the interpolation and will not appear separately in the
118+ stored log entry.
119+
120+
121+ Context array
122+ -------------
123+
124+ The second argument to every log method is a context array. Nextcloud
125+ recognises several special keys:
126+
127+ ``exception ``
128+ If the context contains an ``exception `` key whose value is a
129+ ``\Throwable ``, Nextcloud will serialize the full exception (class name,
130+ message, code, file, line, stack trace and any previous exceptions). **This
131+ is the recommended way to log exceptions: **
132+
133+ .. code-block :: php
134+
135+ try {
136+ $this->doRiskyThing();
137+ } catch (\Exception $e) {
138+ $this->logger->error('Operation failed', ['exception' => $e]);
139+ }
140+
141+ You can provide a custom message as the first argument; if you omit it, the
142+ exception's own message is used.
143+
144+ ``app ``
145+ Identifies the app that produced the message. This is **set automatically **
146+ when you obtain the logger via dependency injection or the ``logger() ``
147+ helper. You can override it explicitly if needed:
148+
149+ .. code-block :: php
150+
151+ $this->logger->info('Cross-app note', ['app' => 'other_app']);
152+
153+ Any other keys you add to the context array are included in the log entry as
154+ additional data:
155+
156+ .. code-block :: php
157+
158+ $this->logger->info('Cron job finished', [
159+ 'duration' => $seconds,
160+ 'items_processed' => $count,
161+ ]);
162+
163+
164+ Logging exceptions
165+ ------------------
166+
167+ As shown above, passing an ``exception `` key in the context triggers detailed
168+ exception serialization. This is preferred over manually calling
169+ ``$e->getMessage() `` because Nextcloud will capture the full stack trace and
170+ chain of previous exceptions.
171+
172+ .. code-block :: php
173+
174+ try {
175+ $this->service->process($data);
176+ } catch (\OCP\DB\Exception $e) {
177+ $this->logger->error('Database operation failed for item {id}', [
178+ 'id' => $itemId,
179+ 'exception' => $e,
180+ ]);
181+ }
182+
183+ You can combine message interpolation with exception logging -- Nextcloud
184+ handles both.
185+
186+
187+ Custom log file
188+ ---------------
189+
190+ If your app needs to write to a **separate log file ** (for example, to keep
191+ verbose debug output out of the main log), you can use
192+ ``OCP\Log\ILogFactory::getCustomPsrLogger() ``:
193+
194+ .. code-block :: php
195+
196+ <?php
197+ namespace OCA\MyApp\Service;
198+
199+ use OCP\Log\ILogFactory;
200+ use Psr\Log\LoggerInterface;
201+
202+ class ImportService {
203+ private LoggerInterface $importLogger;
204+
205+ public function __construct(ILogFactory $logFactory) {
206+ $this->importLogger = $logFactory->getCustomPsrLogger(
207+ '/var/log/nextcloud/import.log'
208+ );
209+ }
210+
211+ public function run(): void {
212+ $this->importLogger->info('Import started');
213+ }
214+ }
215+
216+ The method signature is:
217+
218+ .. code-block :: php
219+
220+ public function getCustomPsrLogger(
221+ string $path,
222+ string $type = 'file',
223+ string $tag = 'Nextcloud'
224+ ): LoggerInterface;
225+
226+ ``$type `` can be ``file `` (default), ``errorlog ``, ``syslog ``, or ``systemd ``.
227+
228+ .. note ::
229+ ``getCustomPsrLogger `` has been available since Nextcloud 22.
230+ The ``$type `` and ``$tag `` parameters were added in Nextcloud 24.
231+
232+
233+ Structured data logging
234+ -----------------------
235+
236+ For advanced use cases, the logger also implements ``OCP\Log\IDataLogger ``
237+ (since Nextcloud 18.0.1), which provides a ``logData() `` method for logging
238+ arbitrary structured data:
239+
240+ .. code-block :: php
241+
242+ use OCP\Log\IDataLogger;
243+
244+ /** @var IDataLogger $logger */
245+ $logger = \OCP\Server::get(\Psr\Log\LoggerInterface::class);
246+ $logger->logData('Sync completed', [
247+ 'provider' => 'caldav',
248+ 'items' => 42,
249+ ], [
250+ 'app' => 'my_app',
251+ 'level' => \OCP\ILogger::INFO,
252+ ]);
253+
254+ The first argument is a message string, the second is the structured data array,
255+ and the third is an optional context array (which accepts the ``app `` and
256+ ``level `` keys described above).
257+
258+
259+ Listening to log events
260+ -----------------------
261+
262+ If your app needs to react when log messages are written (e.g. to forward them
263+ to an external monitoring service), you can listen for the
264+ ``OCP\Log\BeforeMessageLoggedEvent `` event (since Nextcloud 28):
265+
266+ .. code-block :: php
267+
268+ <?php
269+ namespace OCA\MyApp\Listener;
270+
271+ use OCP\EventDispatcher\Event;
272+ use OCP\EventDispatcher\IEventListener;
273+ use OCP\Log\BeforeMessageLoggedEvent;
274+
275+ /** @template-implements IEventListener<BeforeMessageLoggedEvent > */
276+ class LogEventListener implements IEventListener {
277+ public function handle(Event $event): void {
278+ if (!$event instanceof BeforeMessageLoggedEvent) {
279+ return;
280+ }
281+
282+ $app = $event->getApp();
283+ $level = $event->getLevel();
284+ $message = $event->getMessage(); // array with the log entry
285+ // ... forward to external service
286+ }
287+ }
288+
289+ Register the listener in your ``Application::register() `` method:
290+
291+ .. code-block :: php
292+
293+ $context->registerEventListener(
294+ BeforeMessageLoggedEvent::class,
295+ LogEventListener::class
296+ );
297+
44298
45299 Admin audit logging
46300-------------------
@@ -63,3 +317,16 @@ You can easily add a log by simply emitting an ``OCP\Log\Audit\CriticalActionPer
63317 ['name' => 'My App ID']
64318 );
65319 $dispatcher->dispatchTyped($event);
320+
321+ The constructor also accepts an optional third parameter
322+ ``bool $obfuscateParameters = false ``. Set it to ``true `` when the parameters
323+ may contain sensitive information (passwords, tokens, etc.) that should not be
324+ written in clear text to the audit log:
325+
326+ .. code-block :: php
327+
328+ $event = new \OCP\Log\Audit\CriticalActionPerformedEvent(
329+ 'Password changed for user %s',
330+ ['name' => $userId],
331+ true // obfuscate parameters in the audit log
332+ );
0 commit comments