Completed
Push — master ( 8910a3...779675 )
by Jörn Friedrich
09:35
created

Log   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 314
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
dl 0
loc 314
rs 8.2608
c 0
b 0
f 0
wmc 40
lcom 1
cbo 7

12 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 22 4
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() 0 89 22
A interpolate() 0 11 2
A logException() 0 17 4

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. 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
41
/**
42
 * logging utilities
43
 *
44
 * This is a stand in, this should be replaced by a Psr\Log\LoggerInterface
45
 * compatible logger. See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
46
 * for the full interface specification.
47
 *
48
 * MonoLog is an example implementing this interface.
49
 */
50
51
class Log implements ILogger {
52
53
	/** @var string */
54
	private $logger;
55
56
	/** @var SystemConfig */
57
	private $config;
58
59
	/** @var boolean|null cache the result of the log condition check for the request */
60
	private $logConditionSatisfied = null;
61
62
	/** @var Normalizer */
63
	private $normalizer;
64
65
	protected $methodsWithSensitiveParameters = [
66
		// Session/User
67
		'login',
68
		'checkPassword',
69
		'updatePrivateKeyPassword',
70
		'validateUserPass',
71
		'loginWithPassword',
72
73
		// TokenProvider
74
		'getToken',
75
		'isTokenPassword',
76
		'getPassword',
77
		'decryptPassword',
78
		'logClientIn',
79
		'generateToken',
80
		'validateToken',
81
82
		// TwoFactorAuth
83
		'solveChallenge',
84
		'verifyChallenge',
85
86
		//ICrypto
87
		'calculateHMAC',
88
		'encrypt',
89
		'decrypt',
90
91
		//LoginController
92
		'tryLogin'
93
	];
94
95
	/**
96
	 * @param string $logger The logger that should be used
97
	 * @param SystemConfig $config the system config object
98
	 * @param null $normalizer
99
	 */
100
	public function __construct($logger = null, SystemConfig $config = null, $normalizer = null) {
101
		// FIXME: Add this for backwards compatibility, should be fixed at some point probably
102
		if($config === null) {
103
			$config = \OC::$server->getSystemConfig();
104
		}
105
106
		$this->config = $config;
107
108
		// FIXME: Add this for backwards compatibility, should be fixed at some point probably
109
		if($logger === null) {
110
			$this->logger = 'OC\\Log\\'.ucfirst($this->config->getValue('log_type', 'owncloud'));
111
			call_user_func([$this->logger, 'init']);
112
		} else {
113
			$this->logger = $logger;
114
		}
115
		if ($normalizer === null) {
116
			$this->normalizer = new Normalizer();
117
		} else {
118
			$this->normalizer = $normalizer;
119
		}
120
121
	}
122
123
	/**
124
	 * System is unusable.
125
	 *
126
	 * @param string $message
127
	 * @param array $context
128
	 * @return void
129
	 */
130
	public function emergency($message, array $context = []) {
131
		$this->log(Util::FATAL, $message, $context);
132
	}
133
134
	/**
135
	 * Action must be taken immediately.
136
	 *
137
	 * Example: Entire website down, database unavailable, etc. This should
138
	 * trigger the SMS alerts and wake you up.
139
	 *
140
	 * @param string $message
141
	 * @param array $context
142
	 * @return void
143
	 */
144
	public function alert($message, array $context = []) {
145
		$this->log(Util::ERROR, $message, $context);
146
	}
147
148
	/**
149
	 * Critical conditions.
150
	 *
151
	 * Example: Application component unavailable, unexpected exception.
152
	 *
153
	 * @param string $message
154
	 * @param array $context
155
	 * @return void
156
	 */
157
	public function critical($message, array $context = []) {
158
		$this->log(Util::ERROR, $message, $context);
159
	}
160
161
	/**
162
	 * Runtime errors that do not require immediate action but should typically
163
	 * be logged and monitored.
164
	 *
165
	 * @param string $message
166
	 * @param array $context
167
	 * @return void
168
	 */
169
	public function error($message, array $context = []) {
170
		$this->log(Util::ERROR, $message, $context);
171
	}
172
173
	/**
174
	 * Exceptional occurrences that are not errors.
175
	 *
176
	 * Example: Use of deprecated APIs, poor use of an API, undesirable things
177
	 * that are not necessarily wrong.
178
	 *
179
	 * @param string $message
180
	 * @param array $context
181
	 * @return void
182
	 */
183
	public function warning($message, array $context = []) {
184
		$this->log(Util::WARN, $message, $context);
185
	}
186
187
	/**
188
	 * Normal but significant events.
189
	 *
190
	 * @param string $message
191
	 * @param array $context
192
	 * @return void
193
	 */
194
	public function notice($message, array $context = []) {
195
		$this->log(Util::INFO, $message, $context);
196
	}
197
198
	/**
199
	 * Interesting events.
200
	 *
201
	 * Example: User logs in, SQL logs.
202
	 *
203
	 * @param string $message
204
	 * @param array $context
205
	 * @return void
206
	 */
207
	public function info($message, array $context = []) {
208
		$this->log(Util::INFO, $message, $context);
209
	}
210
211
	/**
212
	 * Detailed debug information.
213
	 *
214
	 * @param string $message
215
	 * @param array $context
216
	 * @return void
217
	 */
218
	public function debug($message, array $context = []) {
219
		$this->log(Util::DEBUG, $message, $context);
220
	}
221
222
223
	/**
224
	 * Logs with an arbitrary level.
225
	 *
226
	 * @param mixed $level
227
	 * @param string $message
228
	 * @param array $context
229
	 * @return void
230
	 */
231
	public function log($level, $message, array $context = []) {
232
		$minLevel = min($this->config->getValue('loglevel', Util::WARN), Util::FATAL);
233
		$logConditions = $this->config->getValue('log.conditions', []);
234
		if (empty($logConditions)) {
235
			$logConditions[] = $this->config->getValue('log.condition', []);
236
		}
237
		$logConditionFile = null;
238
239
		if (isset($context['app'])) {
240
			$app = $context['app'];
241
242
			/**
243
			 * check log condition based on the context of each log message
244
			 * once this is met -> change the required log level to debug
245
			 */
246
			if(!empty($logConditions)) {
247
				foreach ($logConditions as $logCondition) {
248
					if(!empty($logCondition['apps'])
249
					   && in_array($app, $logCondition['apps'], true)) {
250
						$minLevel = Util::DEBUG;
251
						if (!empty($logCondition['logfile'])) {
252
							$logConditionFile = $logCondition['logfile'];
253
							break;
254
						}
255
					}
256
				}
257
			}
258
259
		} else {
260
			$app = 'no app in context';
261
		}
262
263
		/**
264
		 * check for a special log condition - this enables an increased log on
265
		 * a per request/user base
266
		 */
267
		if($this->logConditionSatisfied === null) {
268
			// default to false to just process this once per request
269
			$this->logConditionSatisfied = false;
270
			if(!empty($logConditions)) {
271
				foreach ($logConditions as $logCondition) {
272
273
					// check for secret token in the request
274
					if (!empty($logCondition['shared_secret'])) {
275
						$request = \OC::$server->getRequest();
276
277
						// if token is found in the request change set the log condition to satisfied
278
						if ($request && hash_equals($logCondition['shared_secret'], $request->getParam('log_secret', ''))) {
279
							$this->logConditionSatisfied = true;
280
							if (!empty($logCondition['logfile'])) {
281
								$logConditionFile = $logCondition['logfile'];
282
							}
283
							break;
284
						}
285
					}
286
287
					// check for user
288
					if (!empty($logCondition['users'])) {
289
						$userSession = \OC::$server->getUserSession();
290
						if ($userSession instanceof IUserSession) {
291
							$user = $userSession->getUser();
292
293
							// if the user matches set the log condition to satisfied
294
							if ($user !== null && in_array($user->getUID(), $logCondition['users'], true)) {
295
								$this->logConditionSatisfied = true;
296
								if (!empty($logCondition['logfile'])) {
297
									$logConditionFile = $logCondition['logfile'];
298
								}
299
								break;
300
							}
301
						}
302
					}
303
				}
304
			}
305
		}
306
307
		// if log condition is satisfied change the required log level to DEBUG
308
		if($this->logConditionSatisfied) {
309
			$minLevel = Util::DEBUG;
310
		}
311
312
		if ($level >= $minLevel) {
313
314
			$message = $this->interpolate($message, $context);
315
316
			$logger = $this->logger;
317
			call_user_func([$logger, 'write'], $app, $message, $level, $logConditionFile);
318
		}
319
	}
320
321
	/**
322
	 * interpolate $message as defined in PSR-3
323
	 * @param string $message
324
	 * @param array $context
325
	 * @return string
326
	 */
327
	protected function interpolate ($message, array $context) {
328
329
		$replace = [];
330
		foreach ($context as $key => $val) {
331
			$replace['{' . $key . '}'] = $this->normalizer->format($val);
332
		}
333
334
		// interpolate replacement values into the message and return
335
		return strtr($message, $replace);
336
337
	}
338
339
	/**
340
	 * Logs an exception very detailed
341
	 *
342
	 * @param \Exception | \Throwable $exception
343
	 * @param array $context
344
	 * @return void
345
	 * @since 8.2.0
346
	 */
347
	public function logException($exception, array $context = []) {
348
		$exception = [
349
			'Exception' => get_class($exception),
350
			'Message' => $exception->getMessage(),
351
			'Code' => $exception->getCode(),
352
			'Trace' => $exception->getTraceAsString(),
353
			'File' => $exception->getFile(),
354
			'Line' => $exception->getLine(),
355
		];
356
		$exception['Trace'] = preg_replace('!(' . implode('|', $this->methodsWithSensitiveParameters) . ')\(.*\)!', '$1(*** sensitive parameters replaced ***)', $exception['Trace']);
357
		if (\OC::$server->getUserSession() && \OC::$server->getUserSession()->isLoggedIn()) {
358
			$context['userid'] = \OC::$server->getUserSession()->getUser()->getUID();
359
		}
360
		$msg = isset($context['message']) ? $context['message'] : 'Exception';
361
		$msg .= ': ' . json_encode($exception);
362
		$this->error($msg, $context);
363
	}
364
}
365