Passed
Push — master ( 7d851f...e6959b )
by Robin
15:52 queued 12s
created

Log::setEventDispatcher()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2016, ownCloud, Inc.
7
 *
8
 * @author Arthur Schiwon <[email protected]>
9
 * @author Bart Visscher <[email protected]>
10
 * @author Bernhard Posselt <[email protected]>
11
 * @author Christoph Wurst <[email protected]>
12
 * @author Joas Schilling <[email protected]>
13
 * @author Julius Härtl <[email protected]>
14
 * @author Morris Jobke <[email protected]>
15
 * @author Olivier Paroz <[email protected]>
16
 * @author Robin Appelman <[email protected]>
17
 * @author Roeland Jago Douma <[email protected]>
18
 * @author Thomas Citharel <[email protected]>
19
 * @author Thomas Müller <[email protected]>
20
 * @author Victor Dubiniuk <[email protected]>
21
 *
22
 * @license AGPL-3.0
23
 *
24
 * This code is free software: you can redistribute it and/or modify
25
 * it under the terms of the GNU Affero General Public License, version 3,
26
 * as published by the Free Software Foundation.
27
 *
28
 * This program is distributed in the hope that it will be useful,
29
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31
 * GNU Affero General Public License for more details.
32
 *
33
 * You should have received a copy of the GNU Affero General Public License, version 3,
34
 * along with this program. If not, see <http://www.gnu.org/licenses/>
35
 *
36
 */
37
namespace OC;
38
39
use Exception;
40
use Nextcloud\LogNormalizer\Normalizer;
41
use OC\AppFramework\Bootstrap\Coordinator;
42
use OCP\EventDispatcher\IEventDispatcher;
43
use OCP\Log\BeforeMessageLoggedEvent;
44
use OCP\Log\IDataLogger;
45
use Throwable;
46
use function array_merge;
47
use OC\Log\ExceptionSerializer;
48
use OCP\ILogger;
49
use OCP\Log\IFileBased;
50
use OCP\Log\IWriter;
51
use OCP\Support\CrashReport\IRegistry;
52
use function strtr;
53
54
/**
55
 * logging utilities
56
 *
57
 * This is a stand in, this should be replaced by a Psr\Log\LoggerInterface
58
 * compatible logger. See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
59
 * for the full interface specification.
60
 *
61
 * MonoLog is an example implementing this interface.
62
 */
63
class Log implements ILogger, IDataLogger {
0 ignored issues
show
Deprecated Code introduced by
The interface OCP\ILogger has been deprecated: 20.0.0 use the PSR-3 logger \Psr\Log\LoggerInterface ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

63
class Log implements /** @scrutinizer ignore-deprecated */ ILogger, IDataLogger {

This interface has been deprecated. The supplier of the interface has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the interface will be removed and what other interface to use instead.

Loading history...
64
	private IWriter $logger;
65
	private ?SystemConfig $config;
66
	private ?bool $logConditionSatisfied = null;
67
	private ?Normalizer $normalizer;
68
	private ?IRegistry $crashReporters;
69
	private ?IEventDispatcher $eventDispatcher;
70
71
	/**
72
	 * @param IWriter $logger The logger that should be used
73
	 * @param SystemConfig $config the system config object
74
	 * @param Normalizer|null $normalizer
75
	 * @param IRegistry|null $registry
76
	 */
77
	public function __construct(
78
		IWriter $logger,
79
		SystemConfig $config = null,
80
		Normalizer $normalizer = null,
81
		IRegistry $registry = null
82
	) {
83
		// FIXME: Add this for backwards compatibility, should be fixed at some point probably
84
		if ($config === null) {
85
			$config = \OC::$server->getSystemConfig();
86
		}
87
88
		$this->config = $config;
89
		$this->logger = $logger;
90
		if ($normalizer === null) {
91
			$this->normalizer = new Normalizer();
92
		} else {
93
			$this->normalizer = $normalizer;
94
		}
95
		$this->crashReporters = $registry;
96
		$this->eventDispatcher = null;
97
	}
98
99
	public function setEventDispatcher(IEventDispatcher $eventDispatcher) {
100
		$this->eventDispatcher = $eventDispatcher;
101
	}
102
103
	/**
104
	 * System is unusable.
105
	 *
106
	 * @param string $message
107
	 * @param array $context
108
	 * @return void
109
	 */
110
	public function emergency(string $message, array $context = []) {
111
		$this->log(ILogger::FATAL, $message, $context);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::FATAL has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

111
		$this->log(/** @scrutinizer ignore-deprecated */ ILogger::FATAL, $message, $context);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
112
	}
113
114
	/**
115
	 * Action must be taken immediately.
116
	 *
117
	 * Example: Entire website down, database unavailable, etc. This should
118
	 * trigger the SMS alerts and wake you up.
119
	 *
120
	 * @param string $message
121
	 * @param array $context
122
	 * @return void
123
	 */
124
	public function alert(string $message, array $context = []) {
125
		$this->log(ILogger::ERROR, $message, $context);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

125
		$this->log(/** @scrutinizer ignore-deprecated */ ILogger::ERROR, $message, $context);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
126
	}
127
128
	/**
129
	 * Critical conditions.
130
	 *
131
	 * Example: Application component unavailable, unexpected exception.
132
	 *
133
	 * @param string $message
134
	 * @param array $context
135
	 * @return void
136
	 */
137
	public function critical(string $message, array $context = []) {
138
		$this->log(ILogger::ERROR, $message, $context);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

138
		$this->log(/** @scrutinizer ignore-deprecated */ ILogger::ERROR, $message, $context);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
139
	}
140
141
	/**
142
	 * Runtime errors that do not require immediate action but should typically
143
	 * be logged and monitored.
144
	 *
145
	 * @param string $message
146
	 * @param array $context
147
	 * @return void
148
	 */
149
	public function error(string $message, array $context = []) {
150
		$this->log(ILogger::ERROR, $message, $context);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

150
		$this->log(/** @scrutinizer ignore-deprecated */ ILogger::ERROR, $message, $context);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
151
	}
152
153
	/**
154
	 * Exceptional occurrences that are not errors.
155
	 *
156
	 * Example: Use of deprecated APIs, poor use of an API, undesirable things
157
	 * that are not necessarily wrong.
158
	 *
159
	 * @param string $message
160
	 * @param array $context
161
	 * @return void
162
	 */
163
	public function warning(string $message, array $context = []) {
164
		$this->log(ILogger::WARN, $message, $context);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::WARN has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

164
		$this->log(/** @scrutinizer ignore-deprecated */ ILogger::WARN, $message, $context);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
165
	}
166
167
	/**
168
	 * Normal but significant events.
169
	 *
170
	 * @param string $message
171
	 * @param array $context
172
	 * @return void
173
	 */
174
	public function notice(string $message, array $context = []) {
175
		$this->log(ILogger::INFO, $message, $context);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::INFO has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

175
		$this->log(/** @scrutinizer ignore-deprecated */ ILogger::INFO, $message, $context);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
176
	}
177
178
	/**
179
	 * Interesting events.
180
	 *
181
	 * Example: User logs in, SQL logs.
182
	 *
183
	 * @param string $message
184
	 * @param array $context
185
	 * @return void
186
	 */
187
	public function info(string $message, array $context = []) {
188
		$this->log(ILogger::INFO, $message, $context);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::INFO has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

188
		$this->log(/** @scrutinizer ignore-deprecated */ ILogger::INFO, $message, $context);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
189
	}
190
191
	/**
192
	 * Detailed debug information.
193
	 *
194
	 * @param string $message
195
	 * @param array $context
196
	 * @return void
197
	 */
198
	public function debug(string $message, array $context = []) {
199
		$this->log(ILogger::DEBUG, $message, $context);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::DEBUG has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

199
		$this->log(/** @scrutinizer ignore-deprecated */ ILogger::DEBUG, $message, $context);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
200
	}
201
202
203
	/**
204
	 * Logs with an arbitrary level.
205
	 *
206
	 * @param int $level
207
	 * @param string $message
208
	 * @param array $context
209
	 * @return void
210
	 */
211
	public function log(int $level, string $message, array $context = []) {
212
		$minLevel = $this->getLogLevel($context);
213
214
		array_walk($context, [$this->normalizer, 'format']);
215
216
		$app = $context['app'] ?? 'no app in context';
217
		$entry = $this->interpolateMessage($context, $message);
218
219
		if ($this->eventDispatcher) {
220
			$this->eventDispatcher->dispatchTyped(new BeforeMessageLoggedEvent($app, $level, $entry));
221
		}
222
223
		try {
224
			if ($level >= $minLevel) {
225
				$this->writeLog($app, $entry, $level);
226
227
				if ($this->crashReporters !== null) {
228
					$messageContext = array_merge(
229
						$context,
230
						[
231
							'level' => $level
232
						]
233
					);
234
					$this->crashReporters->delegateMessage($entry['message'], $messageContext);
235
				}
236
			} else {
237
				if ($this->crashReporters !== null) {
238
					$this->crashReporters->delegateBreadcrumb($entry['message'], 'log', $context);
239
				}
240
			}
241
		} catch (Throwable $e) {
242
			// make sure we dont hard crash if logging fails
243
		}
244
	}
245
246
	public function getLogLevel($context) {
247
		$logCondition = $this->config->getValue('log.condition', []);
0 ignored issues
show
Bug introduced by
The method getValue() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

247
		/** @scrutinizer ignore-call */ 
248
  $logCondition = $this->config->getValue('log.condition', []);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
248
249
		/**
250
		 * check for a special log condition - this enables an increased log on
251
		 * a per request/user base
252
		 */
253
		if ($this->logConditionSatisfied === null) {
254
			// default to false to just process this once per request
255
			$this->logConditionSatisfied = false;
256
			if (!empty($logCondition)) {
257
				// check for secret token in the request
258
				if (isset($logCondition['shared_secret'])) {
259
					$request = \OC::$server->getRequest();
260
261
					if ($request->getMethod() === 'PUT' &&
262
						!str_contains($request->getHeader('Content-Type'), 'application/x-www-form-urlencoded') &&
263
						!str_contains($request->getHeader('Content-Type'), 'application/json')) {
264
						$logSecretRequest = '';
265
					} else {
266
						$logSecretRequest = $request->getParam('log_secret', '');
267
					}
268
269
					// if token is found in the request change set the log condition to satisfied
270
					if ($request && hash_equals($logCondition['shared_secret'], $logSecretRequest)) {
271
						$this->logConditionSatisfied = true;
272
					}
273
				}
274
275
				// check for user
276
				if (isset($logCondition['users'])) {
277
					$user = \OC::$server->getUserSession()->getUser();
278
279
					// if the user matches set the log condition to satisfied
280
					if ($user !== null && in_array($user->getUID(), $logCondition['users'], true)) {
281
						$this->logConditionSatisfied = true;
282
					}
283
				}
284
			}
285
		}
286
287
		// if log condition is satisfied change the required log level to DEBUG
288
		if ($this->logConditionSatisfied) {
289
			return ILogger::DEBUG;
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::DEBUG has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

289
			return /** @scrutinizer ignore-deprecated */ ILogger::DEBUG;

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
290
		}
291
292
		if (isset($context['app'])) {
293
			$app = $context['app'];
294
295
			/**
296
			 * check log condition based on the context of each log message
297
			 * once this is met -> change the required log level to debug
298
			 */
299
			if (!empty($logCondition)
300
				&& isset($logCondition['apps'])
301
				&& in_array($app, $logCondition['apps'], true)) {
302
				return ILogger::DEBUG;
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::DEBUG has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

302
				return /** @scrutinizer ignore-deprecated */ ILogger::DEBUG;

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
303
			}
304
		}
305
306
		return min($this->config->getValue('loglevel', ILogger::WARN), ILogger::FATAL);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::WARN has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

306
		return min($this->config->getValue('loglevel', /** @scrutinizer ignore-deprecated */ ILogger::WARN), ILogger::FATAL);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
Deprecated Code introduced by
The constant OCP\ILogger::FATAL has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

306
		return min($this->config->getValue('loglevel', ILogger::WARN), /** @scrutinizer ignore-deprecated */ ILogger::FATAL);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
307
	}
308
309
	/**
310
	 * Logs an exception very detailed
311
	 *
312
	 * @param Exception|Throwable $exception
313
	 * @param array $context
314
	 * @return void
315
	 * @since 8.2.0
316
	 */
317
	public function logException(Throwable $exception, array $context = []) {
318
		$app = $context['app'] ?? 'no app in context';
319
		$level = $context['level'] ?? ILogger::ERROR;
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

319
		$level = $context['level'] ?? /** @scrutinizer ignore-deprecated */ ILogger::ERROR;

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
320
321
		$minLevel = $this->getLogLevel($context);
322
		if ($level < $minLevel && ($this->crashReporters === null || !$this->crashReporters->hasReporters())) {
323
			return;
324
		}
325
326
		// if an error is raised before the autoloader is properly setup, we can't serialize exceptions
327
		try {
328
			$serializer = $this->getSerializer();
329
		} catch (Throwable $e) {
330
			$this->error("Failed to load ExceptionSerializer serializer while trying to log " . $exception->getMessage());
331
			return;
332
		}
333
		$data = $context;
334
		unset($data['app']);
335
		unset($data['level']);
336
		$data = array_merge($serializer->serializeException($exception), $data);
337
		$data = $this->interpolateMessage($data, $context['message'] ?? '--', 'CustomMessage');
338
339
340
		array_walk($context, [$this->normalizer, 'format']);
341
342
		if ($this->eventDispatcher) {
343
			$this->eventDispatcher->dispatchTyped(new BeforeMessageLoggedEvent($app, $level, $data));
344
		}
345
346
		try {
347
			if ($level >= $minLevel) {
348
				if (!$this->logger instanceof IFileBased) {
349
					$data = json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_UNESCAPED_SLASHES);
350
				}
351
				$this->writeLog($app, $data, $level);
352
			}
353
354
			$context['level'] = $level;
355
			if (!is_null($this->crashReporters)) {
356
				$this->crashReporters->delegateReport($exception, $context);
357
			}
358
		} catch (Throwable $e) {
359
			// make sure we dont hard crash if logging fails
360
		}
361
	}
362
363
	public function logData(string $message, array $data, array $context = []): void {
364
		$app = $context['app'] ?? 'no app in context';
365
		$level = $context['level'] ?? ILogger::ERROR;
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

365
		$level = $context['level'] ?? /** @scrutinizer ignore-deprecated */ ILogger::ERROR;

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
366
367
		$minLevel = $this->getLogLevel($context);
368
369
		array_walk($context, [$this->normalizer, 'format']);
370
371
		try {
372
			if ($level >= $minLevel) {
373
				$data['message'] = $message;
374
				if (!$this->logger instanceof IFileBased) {
375
					$data = json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_UNESCAPED_SLASHES);
376
				}
377
				$this->writeLog($app, $data, $level);
378
			}
379
380
			$context['level'] = $level;
381
		} catch (Throwable $e) {
382
			// make sure we dont hard crash if logging fails
383
			error_log('Error when trying to log exception: ' . $e->getMessage() . ' ' . $e->getTraceAsString());
384
		}
385
	}
386
387
	/**
388
	 * @param string $app
389
	 * @param string|array $entry
390
	 * @param int $level
391
	 */
392
	protected function writeLog(string $app, $entry, int $level) {
393
		$this->logger->write($app, $entry, $level);
394
	}
395
396
	public function getLogPath():string {
397
		if ($this->logger instanceof IFileBased) {
398
			return $this->logger->getLogFilePath();
0 ignored issues
show
Bug introduced by
The method getLogFilePath() does not exist on OCP\Log\IWriter. It seems like you code against a sub-type of OCP\Log\IWriter such as OC\Log\File. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

398
			return $this->logger->/** @scrutinizer ignore-call */ getLogFilePath();
Loading history...
399
		}
400
		throw new \RuntimeException('Log implementation has no path');
401
	}
402
403
	/**
404
	 * Interpolate $message as defined in PSR-3
405
	 *
406
	 * Returns an array containing the context without the interpolated
407
	 * parameters placeholders and the message as the 'message' - or
408
	 * user-defined - key.
409
	 */
410
	private function interpolateMessage(array $context, string $message, string $messageKey = 'message'): array {
411
		$replace = [];
412
		$usedContextKeys = [];
413
		foreach ($context as $key => $val) {
414
			$fullKey = '{' . $key . '}';
415
			$replace[$fullKey] = $val;
416
			if (str_contains($message, $fullKey)) {
417
				$usedContextKeys[$key] = true;
418
			}
419
		}
420
		return array_merge(array_diff_key($context, $usedContextKeys), [$messageKey => strtr($message, $replace)]);
421
	}
422
423
	/**
424
	 * @throws Throwable
425
	 */
426
	protected function getSerializer(): ExceptionSerializer {
427
		$serializer = new ExceptionSerializer($this->config);
428
		try {
429
			/** @var Coordinator $coordinator */
430
			$coordinator = \OCP\Server::get(Coordinator::class);
431
			foreach ($coordinator->getRegistrationContext()->getSensitiveMethods() as $registration) {
432
				$serializer->enlistSensitiveMethods($registration->getName(), $registration->getValue());
433
			}
434
			// For not every app might be initialized at this time, we cannot assume that the return value
435
			// of getSensitiveMethods() is complete. Running delegates in Coordinator::registerApps() is
436
			// not possible due to dependencies on the one hand. On the other it would work only with
437
			// adding public methods to the PsrLoggerAdapter and this class.
438
			// Thus, serializer cannot be a property.
439
		} catch (Throwable $t) {
440
			// ignore app-defined sensitive methods in this case - they weren't loaded anyway
441
		}
442
		return $serializer;
443
	}
444
}
445