Completed
Pull Request — master (#32513)
by Jörn Friedrich
24:24 queued 13:24
created

Log   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 389
Duplicated Lines 2.57 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 0
Metric Value
dl 10
loc 389
rs 8.4
c 0
b 0
f 0
wmc 50
lcom 1
cbo 9

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 32 5
A emergency() 0 3 1
A alert() 0 3 1
A critical() 0 3 1
A error() 0 3 1
A warning() 0 3 1
A notice() 0 3 1
A info() 0 3 1
A debug() 0 3 1
F log() 10 135 29
A interpolate() 0 9 2
B logException() 0 28 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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
 * @author Bart Visscher <[email protected]>
4
 * @author Bernhard Posselt <[email protected]>
5
 * @author Joas Schilling <[email protected]>
6
 * @author Jörn Friedrich Dreyer <[email protected]>
7
 * @author Juan Pablo Villafañez <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 * @author Olivier Paroz <[email protected]>
11
 * @author Robin Appelman <[email protected]>
12
 * @author Roeland Jago Douma <[email protected]>
13
 * @author Thomas Müller <[email protected]>
14
 * @author Victor Dubiniuk <[email protected]>
15
 *
16
 * @copyright Copyright (c) 2018, ownCloud GmbH
17
 * @license AGPL-3.0
18
 *
19
 * This code is free software: you can redistribute it and/or modify
20
 * it under the terms of the GNU Affero General Public License, version 3,
21
 * as published by the Free Software Foundation.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
 * GNU Affero General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU Affero General Public License, version 3,
29
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
30
 *
31
 */
32
33
namespace OC;
34
35
use InterfaSys\LogNormalizer\Normalizer;
36
37
use \OCP\ILogger;
38
use OCP\IUserSession;
39
use OCP\Util;
40
use Symfony\Component\EventDispatcher\GenericEvent;
41
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
42
43
/**
44
 * logging utilities
45
 *
46
 * This is a stand in, this should be replaced by a Psr\Log\LoggerInterface
47
 * compatible logger. See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
48
 * for the full interface specification.
49
 *
50
 * MonoLog is an example implementing this interface.
51
 */
52
53
class Log implements ILogger {
54
55
	/** @var string */
56
	private $logger;
57
58
	/** @var SystemConfig */
59
	private $config;
60
61
	/** @var EventDispatcherInterface */
62
	private $eventDispatcher;
63
64
	/** @var boolean|null cache the result of the log condition check for the request */
65
	private $logConditionSatisfied = null;
66
67
	/** @var Normalizer */
68
	private $normalizer;
69
70
	/**
71
	 * Flag whether we are within the event block
72
	 *
73
	 * @var bool
74
	 */
75
	private $inEvent = false;
76
77
	protected $methodsWithSensitiveParameters = [
78
		// Session/User
79
		'login',
80
		'checkPassword',
81
		'updatePrivateKeyPassword',
82
		'validateUserPass',
83
		'loginWithPassword',
84
85
		// TokenProvider
86
		'getToken',
87
		'isTokenPassword',
88
		'getPassword',
89
		'decryptPassword',
90
		'logClientIn',
91
		'generateToken',
92
		'validateToken',
93
94
		// TwoFactorAuth
95
		'solveChallenge',
96
		'verifyChallenge',
97
98
		//ICrypto
99
		'calculateHMAC',
100
		'encrypt',
101
		'decrypt',
102
103
		//LoginController
104
		'tryLogin'
105
	];
106
107
	/**
108
	 * @param string $logger The logger that should be used
109
	 * @param SystemConfig $config the system config object
110
	 * @param null $normalizer
111
	 * @param EventDispatcherInterface $eventDispatcher event dispatcher
112
	 */
113
	public function __construct(
114
		$logger = null,
115
		SystemConfig $config = null,
116
		$normalizer = null,
117
		EventDispatcherInterface $eventDispatcher = null
118
	) {
119
		// FIXME: Add this for backwards compatibility, should be fixed at some point probably
120
		if ($config === null) {
121
			$config = \OC::$server->getSystemConfig();
122
		}
123
124
		$this->config = $config;
125
126
		// FIXME: Add this for backwards compatibility, should be fixed at some point probably
127
		if ($logger === null) {
128
			$this->logger = 'OC\\Log\\'.\ucfirst($this->config->getValue('log_type', 'owncloud'));
129
			\call_user_func([$this->logger, 'init']);
130
		} else {
131
			$this->logger = $logger;
132
		}
133
		if ($normalizer === null) {
134
			$this->normalizer = new Normalizer();
135
		} else {
136
			$this->normalizer = $normalizer;
137
		}
138
139
		if ($eventDispatcher === null) {
140
			$this->eventDispatcher = \OC::$server->getEventDispatcher();
141
		} else {
142
			$this->eventDispatcher = $eventDispatcher;
143
		}
144
	}
145
146
	/**
147
	 * System is unusable.
148
	 *
149
	 * @param string $message
150
	 * @param array $context
151
	 * @return void
152
	 */
153
	public function emergency($message, array $context = []) {
154
		$this->log(Util::FATAL, $message, $context);
155
	}
156
157
	/**
158
	 * Action must be taken immediately.
159
	 *
160
	 * Example: Entire website down, database unavailable, etc. This should
161
	 * trigger the SMS alerts and wake you up.
162
	 *
163
	 * @param string $message
164
	 * @param array $context
165
	 * @return void
166
	 */
167
	public function alert($message, array $context = []) {
168
		$this->log(Util::ERROR, $message, $context);
169
	}
170
171
	/**
172
	 * Critical conditions.
173
	 *
174
	 * Example: Application component unavailable, unexpected exception.
175
	 *
176
	 * @param string $message
177
	 * @param array $context
178
	 * @return void
179
	 */
180
	public function critical($message, array $context = []) {
181
		$this->log(Util::ERROR, $message, $context);
182
	}
183
184
	/**
185
	 * Runtime errors that do not require immediate action but should typically
186
	 * be logged and monitored.
187
	 *
188
	 * @param string $message
189
	 * @param array $context
190
	 * @return void
191
	 */
192
	public function error($message, array $context = []) {
193
		$this->log(Util::ERROR, $message, $context);
194
	}
195
196
	/**
197
	 * Exceptional occurrences that are not errors.
198
	 *
199
	 * Example: Use of deprecated APIs, poor use of an API, undesirable things
200
	 * that are not necessarily wrong.
201
	 *
202
	 * @param string $message
203
	 * @param array $context
204
	 * @return void
205
	 */
206
	public function warning($message, array $context = []) {
207
		$this->log(Util::WARN, $message, $context);
208
	}
209
210
	/**
211
	 * Normal but significant events.
212
	 *
213
	 * @param string $message
214
	 * @param array $context
215
	 * @return void
216
	 */
217
	public function notice($message, array $context = []) {
218
		$this->log(Util::INFO, $message, $context);
219
	}
220
221
	/**
222
	 * Interesting events.
223
	 *
224
	 * Example: User logs in, SQL logs.
225
	 *
226
	 * @param string $message
227
	 * @param array $context
228
	 * @return void
229
	 */
230
	public function info($message, array $context = []) {
231
		$this->log(Util::INFO, $message, $context);
232
	}
233
234
	/**
235
	 * Detailed debug information.
236
	 *
237
	 * @param string $message
238
	 * @param array $context
239
	 * @return void
240
	 */
241
	public function debug($message, array $context = []) {
242
		$this->log(Util::DEBUG, $message, $context);
243
	}
244
245
	/**
246
	 * Logs with an arbitrary level.
247
	 *
248
	 * @param mixed $level
249
	 * @param string $message
250
	 * @param array $context
251
	 * @return void
252
	 */
253
	public function log($level, $message, array $context = []) {
254
		$minLevel = \min($this->config->getValue('loglevel', Util::WARN), Util::FATAL);
255
		$logConditions = $this->config->getValue('log.conditions', []);
256
		if (empty($logConditions)) {
257
			$logConditions[] = $this->config->getValue('log.condition', []);
258
		}
259
		$logConditionFile = null;
260
261
		$extraFields = [];
262
		if (isset($context['extraFields'])) {
263
			$extraFields = $context['extraFields'];
264
			unset($context['extraFields']);
265
		}
266
267
		$exception = null;
268
		if (isset($context['exception'])) {
269
			$exception = $context['exception'];
270
			unset($context['exception']);
271
		}
272
273
		if (isset($context['app'])) {
274
			$app = $context['app'];
275
276
			/**
277
			 * check log condition based on the context of each log message
278
			 * once this is met -> change the required log level to debug
279
			 */
280
			if (!empty($logConditions)) {
281
				foreach ($logConditions as $logCondition) {
282
					if (!empty($logCondition['apps'])
283
					   && \in_array($app, $logCondition['apps'], true)) {
284
						$minLevel = Util::DEBUG;
285
						if (!empty($logCondition['logfile'])) {
286
							$logConditionFile = $logCondition['logfile'];
287
							break;
288
						}
289
					}
290
				}
291
			}
292
		} else {
293
			$app = 'no app in context';
294
		}
295
296
		/**
297
		 * check for a special log condition - this enables an increased log on
298
		 * a per request/user base
299
		 */
300
		if ($this->logConditionSatisfied === null) {
301
			// default to false to just process this once per request
302
			$this->logConditionSatisfied = false;
303
			if (!empty($logConditions)) {
304
				foreach ($logConditions as $logCondition) {
305
306
					// check for secret token in the request
307
					if (!empty($logCondition['shared_secret'])) {
308
						$request = \OC::$server->getRequest();
309
310
						// if token is found in the request change set the log condition to satisfied
311
						if ($request && \hash_equals($logCondition['shared_secret'], $request->getParam('log_secret', ''))) {
312
							$this->logConditionSatisfied = true;
313
							if (!empty($logCondition['logfile'])) {
314
								$logConditionFile = $logCondition['logfile'];
315
							}
316
							break;
317
						}
318
					}
319
320
					// check for user
321
					if (!empty($logCondition['users'])) {
322
						$userSession = \OC::$server->getUserSession();
323
						if ($userSession instanceof IUserSession) {
324
							$user = $userSession->getUser();
325
326
							// if the user matches set the log condition to satisfied
327
							if ($user !== null && \in_array($user->getUID(), $logCondition['users'], true)) {
328
								$this->logConditionSatisfied = true;
329
								if (!empty($logCondition['logfile'])) {
330
									$logConditionFile = $logCondition['logfile'];
331
								}
332
								break;
333
							}
334
						}
335
					}
336
				}
337
			}
338
		}
339
340
		// if log condition is satisfied change the required log level to DEBUG
341
		if ($this->logConditionSatisfied) {
342
			$minLevel = Util::DEBUG;
343
		}
344
345
		$skipEvents = false;
346
		// avoid infinite loop in case an event handler logs something
347
		if ($this->inEvent) {
348
			$skipEvents = true;
349
		}
350
351
		$formattedMessage = $this->interpolate($message, $context);
352
353
		$eventArgs = [
354
			'app' => $app,
355
			'loglevel' => $level,
356
			'message' => $message,
357
			'formattedMessage' => $formattedMessage,
358
			'context' => $context,
359
			'extraFields' => $extraFields,
360
			'exception' => $exception
361
		];
362
363
		// note: regardless of log level we let listeners receive messages
364
		$this->inEvent = true;
365 View Code Duplication
		if (!$skipEvents) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
366
			$event = new GenericEvent(null);
367
			$event->setArguments($eventArgs);
368
			$this->eventDispatcher->dispatch('log.beforewrite', $event);
369
		}
370
371
		if ($level >= $minLevel) {
372
			$logger = $this->logger;
373
			// check if logger supports extra fields
374
			if (!empty($extraFields) && \is_callable($logger, 'writeExtra')) {
375
				\call_user_func([$logger, 'writeExtra'], $app, $formattedMessage, $level, $logConditionFile, $extraFields);
376
			} else {
377
				\call_user_func([$logger, 'write'], $app, $formattedMessage, $level, $logConditionFile);
378
			}
379
		}
380
381 View Code Duplication
		if (!$skipEvents) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
382
			$event = new GenericEvent(null);
383
			$event->setArguments($eventArgs);
384
			$this->eventDispatcher->dispatch('log.afterwrite', $event);
385
		}
386
		$this->inEvent = false;
387
	}
388
389
	/**
390
	 * interpolate $message as defined in PSR-3
391
	 * @param string $message
392
	 * @param array $context
393
	 * @return string
394
	 */
395
	protected function interpolate($message, array $context) {
396
		$replace = [];
397
		foreach ($context as $key => $val) {
398
			$replace['{' . $key . '}'] = $this->normalizer->format($val);
399
		}
400
401
		// interpolate replacement values into the message and return
402
		return \strtr($message, $replace);
403
	}
404
405
	/**
406
	 * Logs an exception very detailed
407
	 *
408
	 * @param \Exception | \Throwable $exception
409
	 * @param array $context
410
	 * @return void
411
	 * @since 8.2.0
412
	 */
413
	public function logException($exception, array $context = []) {
414
		$context['exception'] =  $exception;
415
		$level = Util::ERROR;
416
		if (isset($context['level'])) {
417
			$level = $context['level'];
418
			unset($context['level']);
419
		}
420
		$exception = [
421
			'Exception' => \get_class($exception),
422
			'Message' => $exception->getMessage(),
423
			'Code' => $exception->getCode(),
424
			'Trace' => $exception->getTraceAsString(),
425
			'File' => $exception->getFile(),
426
			'Line' => $exception->getLine(),
427
		];
428
		$exception['Trace'] = \preg_replace('!(' . \implode('|', $this->methodsWithSensitiveParameters) . ')\(.*\)!', '$1(*** sensitive parameters replaced ***)', $exception['Trace']);
429
		if (\OC::$server->getUserSession() && \OC::$server->getUserSession()->isLoggedIn()) {
430
			$context['userid'] = \OC::$server->getUserSession()->getUser()->getUID();
431
		}
432
		$msg = isset($context['message']) ? $context['message'] : 'Exception';
433
		$msg .= ': ' . \json_encode($exception);
434
		$this->log($level, $msg, $context);
435
		// also log previous exception
436
		if ($context['exception']->getPrevious()) {
437
			$context['message'] = 'Caused by';
438
			$this->logException($context['exception']->getPrevious(), $context);
439
		}
440
	}
441
}
442