Completed
Pull Request — master (#6643)
by Morris
13:55
created

Log::logException()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 22
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 18
c 0
b 0
f 0
nc 8
nop 2
dl 0
loc 22
rs 8.9197
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bart Visscher <[email protected]>
6
 * @author Bernhard Posselt <[email protected]>
7
 * @author Joas Schilling <[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
 * @license AGPL-3.0
17
 *
18
 * This code is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License, version 3,
20
 * as published by the Free Software Foundation.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
 * GNU Affero General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License, version 3,
28
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
29
 *
30
 */
31
32
namespace OC;
33
34
use InterfaSys\LogNormalizer\Normalizer;
35
36
use \OCP\ILogger;
37
use OCP\Util;
38
39
/**
40
 * logging utilities
41
 *
42
 * This is a stand in, this should be replaced by a Psr\Log\LoggerInterface
43
 * compatible logger. See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
44
 * for the full interface specification.
45
 *
46
 * MonoLog is an example implementing this interface.
47
 */
48
49
class Log implements ILogger {
50
51
	/** @var string */
52
	private $logger;
53
54
	/** @var SystemConfig */
55
	private $config;
56
57
	/** @var boolean|null cache the result of the log condition check for the request */
58
	private $logConditionSatisfied = null;
59
60
	/** @var Normalizer */
61
	private $normalizer;
62
63
	protected $methodsWithSensitiveParameters = [
64
		// Session/User
65
		'completeLogin',
66
		'login',
67
		'checkPassword',
68
		'checkPasswordNoLogging',
69
		'loginWithPassword',
70
		'updatePrivateKeyPassword',
71
		'validateUserPass',
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
		'confirmPassword',
94
95
		// LDAP
96
		'bind',
97
		'areCredentialsValid',
98
		'invokeLDAPMethod',
99
	];
100
101
	/**
102
	 * @param string $logger The logger that should be used
103
	 * @param SystemConfig $config the system config object
104
	 * @param null $normalizer
105
	 */
106
	public function __construct($logger = null, SystemConfig $config = null, $normalizer = null) {
107
		// FIXME: Add this for backwards compatibility, should be fixed at some point probably
108
		if($config === null) {
109
			$config = \OC::$server->getSystemConfig();
110
		}
111
112
		$this->config = $config;
113
114
		// FIXME: Add this for backwards compatibility, should be fixed at some point probably
115
		if($logger === null) {
116
			$logType = $this->config->getValue('log_type', 'file');
117
			$this->logger = static::getLogClass($logType);
118
			call_user_func(array($this->logger, 'init'));
119
		} else {
120
			$this->logger = $logger;
121
		}
122
		if ($normalizer === null) {
123
			$this->normalizer = new Normalizer();
124
		} else {
125
			$this->normalizer = $normalizer;
126
		}
127
128
	}
129
130
	/**
131
	 * System is unusable.
132
	 *
133
	 * @param string $message
134
	 * @param array $context
135
	 * @return void
136
	 */
137
	public function emergency($message, array $context = array()) {
138
		$this->log(Util::FATAL, $message, $context);
139
	}
140
141
	/**
142
	 * Action must be taken immediately.
143
	 *
144
	 * Example: Entire website down, database unavailable, etc. This should
145
	 * trigger the SMS alerts and wake you up.
146
	 *
147
	 * @param string $message
148
	 * @param array $context
149
	 * @return void
150
	 */
151
	public function alert($message, array $context = array()) {
152
		$this->log(Util::ERROR, $message, $context);
153
	}
154
155
	/**
156
	 * Critical conditions.
157
	 *
158
	 * Example: Application component unavailable, unexpected exception.
159
	 *
160
	 * @param string $message
161
	 * @param array $context
162
	 * @return void
163
	 */
164
	public function critical($message, array $context = array()) {
165
		$this->log(Util::ERROR, $message, $context);
166
	}
167
168
	/**
169
	 * Runtime errors that do not require immediate action but should typically
170
	 * be logged and monitored.
171
	 *
172
	 * @param string $message
173
	 * @param array $context
174
	 * @return void
175
	 */
176
	public function error($message, array $context = array()) {
177
		$this->log(Util::ERROR, $message, $context);
178
	}
179
180
	/**
181
	 * Exceptional occurrences that are not errors.
182
	 *
183
	 * Example: Use of deprecated APIs, poor use of an API, undesirable things
184
	 * that are not necessarily wrong.
185
	 *
186
	 * @param string $message
187
	 * @param array $context
188
	 * @return void
189
	 */
190
	public function warning($message, array $context = array()) {
191
		$this->log(Util::WARN, $message, $context);
192
	}
193
194
	/**
195
	 * Normal but significant events.
196
	 *
197
	 * @param string $message
198
	 * @param array $context
199
	 * @return void
200
	 */
201
	public function notice($message, array $context = array()) {
202
		$this->log(Util::INFO, $message, $context);
203
	}
204
205
	/**
206
	 * Interesting events.
207
	 *
208
	 * Example: User logs in, SQL logs.
209
	 *
210
	 * @param string $message
211
	 * @param array $context
212
	 * @return void
213
	 */
214
	public function info($message, array $context = array()) {
215
		$this->log(Util::INFO, $message, $context);
216
	}
217
218
	/**
219
	 * Detailed debug information.
220
	 *
221
	 * @param string $message
222
	 * @param array $context
223
	 * @return void
224
	 */
225
	public function debug($message, array $context = array()) {
226
		$this->log(Util::DEBUG, $message, $context);
227
	}
228
229
230
	/**
231
	 * Logs with an arbitrary level.
232
	 *
233
	 * @param mixed $level
234
	 * @param string $message
235
	 * @param array $context
236
	 * @return void
237
	 */
238
	public function log($level, $message, array $context = array()) {
239
		$minLevel = min($this->config->getValue('loglevel', Util::WARN), Util::FATAL);
240
		$logCondition = $this->config->getValue('log.condition', []);
241
242
		array_walk($context, [$this->normalizer, 'format']);
243
244
		if (isset($context['app'])) {
245
			$app = $context['app'];
246
247
			/**
248
			 * check log condition based on the context of each log message
249
			 * once this is met -> change the required log level to debug
250
			 */
251
			if(!empty($logCondition)
252
				&& isset($logCondition['apps'])
253
				&& in_array($app, $logCondition['apps'], true)) {
254
				$minLevel = Util::DEBUG;
255
			}
256
257
		} else {
258
			$app = 'no app in context';
259
		}
260
		// interpolate $message as defined in PSR-3
261
		$replace = array();
262
		foreach ($context as $key => $val) {
263
			$replace['{' . $key . '}'] = $val;
264
		}
265
266
		// interpolate replacement values into the message and return
267
		$message = strtr($message, $replace);
268
269
		/**
270
		 * check for a special log condition - this enables an increased log on
271
		 * a per request/user base
272
		 */
273
		if($this->logConditionSatisfied === null) {
274
			// default to false to just process this once per request
275
			$this->logConditionSatisfied = false;
276
			if(!empty($logCondition)) {
277
278
				// check for secret token in the request
279
				if(isset($logCondition['shared_secret'])) {
280
					$request = \OC::$server->getRequest();
281
282
					// if token is found in the request change set the log condition to satisfied
283
					if($request && hash_equals($logCondition['shared_secret'], $request->getParam('log_secret', ''))) {
284
						$this->logConditionSatisfied = true;
285
					}
286
				}
287
288
				// check for user
289
				if(isset($logCondition['users'])) {
290
					$user = \OC::$server->getUserSession()->getUser();
291
292
					// if the user matches set the log condition to satisfied
293
					if($user !== null && in_array($user->getUID(), $logCondition['users'], true)) {
294
						$this->logConditionSatisfied = true;
295
					}
296
				}
297
			}
298
		}
299
300
		// if log condition is satisfied change the required log level to DEBUG
301
		if($this->logConditionSatisfied) {
302
			$minLevel = Util::DEBUG;
303
		}
304
305
		if ($level >= $minLevel) {
306
			$logger = $this->logger;
307
			call_user_func(array($logger, 'write'), $app, $message, $level);
308
		}
309
	}
310
311
	/**
312
	 * Logs an exception very detailed
313
	 *
314
	 * @param \Exception|\Throwable $exception
315
	 * @param array $context
316
	 * @return void
317
	 * @since 8.2.0
318
	 */
319
	public function logException($exception, array $context = array()) {
320
		$level = Util::ERROR;
321
		if (isset($context['level'])) {
322
			$level = $context['level'];
323
			unset($context['level']);
324
		}
325
		$data = array(
326
			'Exception' => get_class($exception),
327
			'Message' => $exception->getMessage(),
328
			'Code' => $exception->getCode(),
329
			'Trace' => $exception->getTraceAsString(),
330
			'File' => $exception->getFile(),
331
			'Line' => $exception->getLine(),
332
		);
333
		$data['Trace'] = preg_replace('!(' . implode('|', $this->methodsWithSensitiveParameters) . ')\(.*\)!', '$1(*** sensitive parameters replaced ***)', $data['Trace']);
334
		if ($exception instanceof HintException) {
335
			$data['Hint'] = $exception->getHint();
336
		}
337
		$msg = isset($context['message']) ? $context['message'] : 'Exception';
338
		$msg .= ': ' . json_encode($data);
339
		$this->log($level, $msg, $context);
340
	}
341
342
	/**
343
	 * @param string $logType
344
	 * @return string
345
	 * @internal
346
	 */
347
	public static function getLogClass($logType) {
348
		switch (strtolower($logType)) {
349
			case 'errorlog':
350
				return \OC\Log\Errorlog::class;
351
			case 'syslog':
352
				return \OC\Log\Syslog::class;
353
			case 'file':
354
				return \OC\Log\File::class;
355
356
			// Backwards compatibility for old and fallback for unknown log types
357
			case 'owncloud':
358
			case 'nextcloud':
359
			default:
360
				return \OC\Log\File::class;
361
		}
362
	}
363
}
364