Passed
Push — master ( 052f09...8557c6 )
by Julius
15:44 queued 12s
created

Log   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 361
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 121
c 1
b 0
f 0
dl 0
loc 361
rs 6
wmc 55

17 Methods

Rating   Name   Duplication   Size   Complexity  
A writeLog() 0 2 1
A getLogPath() 0 5 2
A logData() 0 19 4
B logException() 0 38 9
A __construct() 0 14 3
C getLogLevel() 0 62 17
A info() 0 2 1
A notice() 0 2 1
A getSerializer() 0 17 3
A critical() 0 2 1
A interpolateMessage() 0 11 3
A error() 0 2 1
A debug() 0 2 1
A alert() 0 2 1
A emergency() 0 2 1
A log() 0 27 5
A warning() 0 2 1

How to fix   Complexity   

Complex Class

Complex classes like Log often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Log, and based on these observations, apply Extract Interface, too.

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\Log\IDataLogger;
43
use Throwable;
44
use function array_merge;
45
use OC\Log\ExceptionSerializer;
46
use OCP\ILogger;
47
use OCP\Log\IFileBased;
48
use OCP\Log\IWriter;
49
use OCP\Support\CrashReport\IRegistry;
50
use function strtr;
51
52
/**
53
 * logging utilities
54
 *
55
 * This is a stand in, this should be replaced by a Psr\Log\LoggerInterface
56
 * compatible logger. See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
57
 * for the full interface specification.
58
 *
59
 * MonoLog is an example implementing this interface.
60
 */
61
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

61
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...
62
	private IWriter $logger;
63
	private ?SystemConfig $config;
64
	private ?bool $logConditionSatisfied = null;
65
	private ?Normalizer $normalizer;
66
	private ?IRegistry $crashReporters;
67
68
	/**
69
	 * @param IWriter $logger The logger that should be used
70
	 * @param SystemConfig $config the system config object
71
	 * @param Normalizer|null $normalizer
72
	 * @param IRegistry|null $registry
73
	 */
74
	public function __construct(IWriter $logger, SystemConfig $config = null, Normalizer $normalizer = null, IRegistry $registry = null) {
75
		// FIXME: Add this for backwards compatibility, should be fixed at some point probably
76
		if ($config === null) {
77
			$config = \OC::$server->getSystemConfig();
78
		}
79
80
		$this->config = $config;
81
		$this->logger = $logger;
82
		if ($normalizer === null) {
83
			$this->normalizer = new Normalizer();
84
		} else {
85
			$this->normalizer = $normalizer;
86
		}
87
		$this->crashReporters = $registry;
88
	}
89
90
	/**
91
	 * System is unusable.
92
	 *
93
	 * @param string $message
94
	 * @param array $context
95
	 * @return void
96
	 */
97
	public function emergency(string $message, array $context = []) {
98
		$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

98
		$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...
99
	}
100
101
	/**
102
	 * Action must be taken immediately.
103
	 *
104
	 * Example: Entire website down, database unavailable, etc. This should
105
	 * trigger the SMS alerts and wake you up.
106
	 *
107
	 * @param string $message
108
	 * @param array $context
109
	 * @return void
110
	 */
111
	public function alert(string $message, array $context = []) {
112
		$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

112
		$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...
113
	}
114
115
	/**
116
	 * Critical conditions.
117
	 *
118
	 * Example: Application component unavailable, unexpected exception.
119
	 *
120
	 * @param string $message
121
	 * @param array $context
122
	 * @return void
123
	 */
124
	public function critical(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
	 * Runtime errors that do not require immediate action but should typically
130
	 * be logged and monitored.
131
	 *
132
	 * @param string $message
133
	 * @param array $context
134
	 * @return void
135
	 */
136
	public function error(string $message, array $context = []) {
137
		$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

137
		$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...
138
	}
139
140
	/**
141
	 * Exceptional occurrences that are not errors.
142
	 *
143
	 * Example: Use of deprecated APIs, poor use of an API, undesirable things
144
	 * that are not necessarily wrong.
145
	 *
146
	 * @param string $message
147
	 * @param array $context
148
	 * @return void
149
	 */
150
	public function warning(string $message, array $context = []) {
151
		$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

151
		$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...
152
	}
153
154
	/**
155
	 * Normal but significant events.
156
	 *
157
	 * @param string $message
158
	 * @param array $context
159
	 * @return void
160
	 */
161
	public function notice(string $message, array $context = []) {
162
		$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

162
		$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...
163
	}
164
165
	/**
166
	 * Interesting events.
167
	 *
168
	 * Example: User logs in, SQL logs.
169
	 *
170
	 * @param string $message
171
	 * @param array $context
172
	 * @return void
173
	 */
174
	public function info(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
	 * Detailed debug information.
180
	 *
181
	 * @param string $message
182
	 * @param array $context
183
	 * @return void
184
	 */
185
	public function debug(string $message, array $context = []) {
186
		$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

186
		$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...
187
	}
188
189
190
	/**
191
	 * Logs with an arbitrary level.
192
	 *
193
	 * @param int $level
194
	 * @param string $message
195
	 * @param array $context
196
	 * @return void
197
	 */
198
	public function log(int $level, string $message, array $context = []) {
199
		$minLevel = $this->getLogLevel($context);
200
201
		array_walk($context, [$this->normalizer, 'format']);
202
203
		$app = $context['app'] ?? 'no app in context';
204
		$entry = $this->interpolateMessage($context, $message);
205
206
		try {
207
			if ($level >= $minLevel) {
208
				$this->writeLog($app, $entry, $level);
209
210
				if ($this->crashReporters !== null) {
211
					$messageContext = array_merge(
212
						$context,
213
						[
214
							'level' => $level
215
						]
216
					);
217
					$this->crashReporters->delegateMessage($entry['message'], $messageContext);
218
				}
219
			} else {
220
				if ($this->crashReporters !== null) {
221
					$this->crashReporters->delegateBreadcrumb($entry['message'], 'log', $context);
222
				}
223
			}
224
		} catch (Throwable $e) {
225
			// make sure we dont hard crash if logging fails
226
		}
227
	}
228
229
	public function getLogLevel($context) {
230
		$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

230
		/** @scrutinizer ignore-call */ 
231
  $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...
231
232
		/**
233
		 * check for a special log condition - this enables an increased log on
234
		 * a per request/user base
235
		 */
236
		if ($this->logConditionSatisfied === null) {
237
			// default to false to just process this once per request
238
			$this->logConditionSatisfied = false;
239
			if (!empty($logCondition)) {
240
241
				// check for secret token in the request
242
				if (isset($logCondition['shared_secret'])) {
243
					$request = \OC::$server->getRequest();
244
245
					if ($request->getMethod() === 'PUT' &&
246
						strpos($request->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false &&
247
						strpos($request->getHeader('Content-Type'), 'application/json') === false) {
248
						$logSecretRequest = '';
249
					} else {
250
						$logSecretRequest = $request->getParam('log_secret', '');
251
					}
252
253
					// if token is found in the request change set the log condition to satisfied
254
					if ($request && hash_equals($logCondition['shared_secret'], $logSecretRequest)) {
255
						$this->logConditionSatisfied = true;
256
					}
257
				}
258
259
				// check for user
260
				if (isset($logCondition['users'])) {
261
					$user = \OC::$server->getUserSession()->getUser();
262
263
					// if the user matches set the log condition to satisfied
264
					if ($user !== null && in_array($user->getUID(), $logCondition['users'], true)) {
265
						$this->logConditionSatisfied = true;
266
					}
267
				}
268
			}
269
		}
270
271
		// if log condition is satisfied change the required log level to DEBUG
272
		if ($this->logConditionSatisfied) {
273
			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

273
			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...
274
		}
275
276
		if (isset($context['app'])) {
277
			$app = $context['app'];
278
279
			/**
280
			 * check log condition based on the context of each log message
281
			 * once this is met -> change the required log level to debug
282
			 */
283
			if (!empty($logCondition)
284
				&& isset($logCondition['apps'])
285
				&& in_array($app, $logCondition['apps'], true)) {
286
				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

286
				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...
287
			}
288
		}
289
290
		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

290
		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

290
		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...
291
	}
292
293
	/**
294
	 * Logs an exception very detailed
295
	 *
296
	 * @param Exception|Throwable $exception
297
	 * @param array $context
298
	 * @return void
299
	 * @since 8.2.0
300
	 */
301
	public function logException(Throwable $exception, array $context = []) {
302
		$app = $context['app'] ?? 'no app in context';
303
		$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

303
		$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...
304
305
		$minLevel = $this->getLogLevel($context);
306
		if ($level < $minLevel && ($this->crashReporters === null || !$this->crashReporters->hasReporters())) {
307
			return;
308
		}
309
310
		// if an error is raised before the autoloader is properly setup, we can't serialize exceptions
311
		try {
312
			$serializer = $this->getSerializer();
313
		} catch (Throwable $e) {
314
			$this->error("Failed to load ExceptionSerializer serializer while trying to log " . $exception->getMessage());
315
			return;
316
		}
317
		$data = $context;
318
		unset($data['app']);
319
		unset($data['level']);
320
		$data = array_merge($serializer->serializeException($exception), $data);
321
		$data = $this->interpolateMessage($data, $context['message'] ?? '--', 'CustomMessage');
322
323
324
		array_walk($context, [$this->normalizer, 'format']);
325
326
		try {
327
			if ($level >= $minLevel) {
328
				if (!$this->logger instanceof IFileBased) {
329
					$data = json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_UNESCAPED_SLASHES);
330
				}
331
				$this->writeLog($app, $data, $level);
332
			}
333
334
			$context['level'] = $level;
335
			if (!is_null($this->crashReporters)) {
336
				$this->crashReporters->delegateReport($exception, $context);
337
			}
338
		} catch (Throwable $e) {
339
			// make sure we dont hard crash if logging fails
340
		}
341
	}
342
343
	public function logData(string $message, array $data, array $context = []): void {
344
		$app = $context['app'] ?? 'no app in context';
345
		$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

345
		$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...
346
347
		$minLevel = $this->getLogLevel($context);
348
349
		array_walk($context, [$this->normalizer, 'format']);
350
351
		try {
352
			if ($level >= $minLevel) {
353
				$data['message'] = $message;
354
				if (!$this->logger instanceof IFileBased) {
355
					$data = json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_UNESCAPED_SLASHES);
356
				}
357
				$this->writeLog($app, $data, $level);
358
			}
359
360
			$context['level'] = $level;
361
		} catch (Throwable $e) {
362
			// make sure we dont hard crash if logging fails
363
		}
364
	}
365
366
	/**
367
	 * @param string $app
368
	 * @param string|array $entry
369
	 * @param int $level
370
	 */
371
	protected function writeLog(string $app, $entry, int $level) {
372
		$this->logger->write($app, $entry, $level);
373
	}
374
375
	public function getLogPath():string {
376
		if ($this->logger instanceof IFileBased) {
377
			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

377
			return $this->logger->/** @scrutinizer ignore-call */ getLogFilePath();
Loading history...
378
		}
379
		throw new \RuntimeException('Log implementation has no path');
380
	}
381
382
	/**
383
	 * Interpolate $message as defined in PSR-3
384
	 *
385
	 * Returns an array containing the context without the interpolated
386
	 * parameters placeholders and the message as the 'message' - or
387
	 * user-defined - key.
388
	 */
389
	private function interpolateMessage(array $context, string $message, string $messageKey = 'message'): array {
390
		$replace = [];
391
		$usedContextKeys = [];
392
		foreach ($context as $key => $val) {
393
			$fullKey = '{' . $key . '}';
394
			$replace[$fullKey] = $val;
395
			if (strpos($message, $fullKey) !== false) {
396
				$usedContextKeys[$key] = true;
397
			}
398
		}
399
		return array_merge(array_diff_key($context, $usedContextKeys), [$messageKey => strtr($message, $replace)]);
400
	}
401
402
	/**
403
	 * @throws Throwable
404
	 */
405
	protected function getSerializer(): ExceptionSerializer {
406
		$serializer = new ExceptionSerializer($this->config);
407
		try {
408
			/** @var Coordinator $coordinator */
409
			$coordinator = \OCP\Server::get(Coordinator::class);
410
			foreach ($coordinator->getRegistrationContext()->getSensitiveMethods() as $registration) {
411
				$serializer->enlistSensitiveMethods($registration->getName(), $registration->getValue());
412
			}
413
			// For not every app might be initialized at this time, we cannot assume that the return value
414
			// of getSensitiveMethods() is complete. Running delegates in Coordinator::registerApps() is
415
			// not possible due to dependencies on the one hand. On the other it would work only with
416
			// adding public methods to the PsrLoggerAdapter and this class.
417
			// Thus, serializer cannot be a property.
418
		} catch (Throwable $t) {
419
			// ignore app-defined sensitive methods in this case - they weren't loaded anyway
420
		}
421
		return $serializer;
422
	}
423
}
424