Completed
Push — master ( bba166...f13c2b )
by Morris
31:09 queued 07:51
created

Log::__construct()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 4
nop 4
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
/**
4
 * @copyright Copyright (c) 2016, ownCloud, Inc.
5
 *
6
 * @author Arthur Schiwon <[email protected]>
7
 * @author Bart Visscher <[email protected]>
8
 * @author Bernhard Posselt <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author Johannes Schlichenmaier <[email protected]>
11
 * @author Juan Pablo Villafáñez <[email protected]>
12
 * @author Lukas Reschke <[email protected]>
13
 * @author Morris Jobke <[email protected]>
14
 * @author Olivier Paroz <[email protected]>
15
 * @author Robin Appelman <[email protected]>
16
 * @author Thomas Müller <[email protected]>
17
 * @author Thomas Pulzer <[email protected]>
18
 * @author Victor Dubiniuk <[email protected]>
19
 *
20
 * @license AGPL-3.0
21
 *
22
 * This code is free software: you can redistribute it and/or modify
23
 * it under the terms of the GNU Affero General Public License, version 3,
24
 * as published by the Free Software Foundation.
25
 *
26
 * This program is distributed in the hope that it will be useful,
27
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29
 * GNU Affero General Public License for more details.
30
 *
31
 * You should have received a copy of the GNU Affero General Public License, version 3,
32
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
33
 *
34
 */
35
36
namespace OC;
37
38
use InterfaSys\LogNormalizer\Normalizer;
39
40
use OC\Log\ExceptionSerializer;
41
use OCP\Log\IFileBased;
42
use OCP\Log\IWriter;
43
use OCP\ILogger;
44
use OCP\Support\CrashReport\IRegistry;
45
use OCP\Util;
46
47
/**
48
 * logging utilities
49
 *
50
 * This is a stand in, this should be replaced by a Psr\Log\LoggerInterface
51
 * compatible logger. See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
52
 * for the full interface specification.
53
 *
54
 * MonoLog is an example implementing this interface.
55
 */
56
class Log implements ILogger {
57
58
	/** @var IWriter */
59
	private $logger;
60
61
	/** @var SystemConfig */
62
	private $config;
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
	/** @var IRegistry */
71
	private $crashReporters;
72
73
	/**
74
	 * @param IWriter $logger The logger that should be used
75
	 * @param SystemConfig $config the system config object
76
	 * @param Normalizer|null $normalizer
77
	 * @param IRegistry|null $registry
78
	 */
79
	public function __construct(IWriter $logger, SystemConfig $config = null, $normalizer = null, IRegistry $registry = null) {
80
		// FIXME: Add this for backwards compatibility, should be fixed at some point probably
81
		if ($config === null) {
82
			$config = \OC::$server->getSystemConfig();
83
		}
84
85
		$this->config = $config;
86
		$this->logger = $logger;
87
		if ($normalizer === null) {
88
			$this->normalizer = new Normalizer();
89
		} else {
90
			$this->normalizer = $normalizer;
91
		}
92
		$this->crashReporters = $registry;
93
	}
94
95
	/**
96
	 * System is unusable.
97
	 *
98
	 * @param string $message
99
	 * @param array $context
100
	 * @return void
101
	 */
102
	public function emergency(string $message, array $context = []) {
103
		$this->log(ILogger::FATAL, $message, $context);
104
	}
105
106
	/**
107
	 * Action must be taken immediately.
108
	 *
109
	 * Example: Entire website down, database unavailable, etc. This should
110
	 * trigger the SMS alerts and wake you up.
111
	 *
112
	 * @param string $message
113
	 * @param array $context
114
	 * @return void
115
	 */
116
	public function alert(string $message, array $context = []) {
117
		$this->log(ILogger::ERROR, $message, $context);
118
	}
119
120
	/**
121
	 * Critical conditions.
122
	 *
123
	 * Example: Application component unavailable, unexpected exception.
124
	 *
125
	 * @param string $message
126
	 * @param array $context
127
	 * @return void
128
	 */
129
	public function critical(string $message, array $context = []) {
130
		$this->log(ILogger::ERROR, $message, $context);
131
	}
132
133
	/**
134
	 * Runtime errors that do not require immediate action but should typically
135
	 * be logged and monitored.
136
	 *
137
	 * @param string $message
138
	 * @param array $context
139
	 * @return void
140
	 */
141
	public function error(string $message, array $context = []) {
142
		$this->log(ILogger::ERROR, $message, $context);
143
	}
144
145
	/**
146
	 * Exceptional occurrences that are not errors.
147
	 *
148
	 * Example: Use of deprecated APIs, poor use of an API, undesirable things
149
	 * that are not necessarily wrong.
150
	 *
151
	 * @param string $message
152
	 * @param array $context
153
	 * @return void
154
	 */
155
	public function warning(string $message, array $context = []) {
156
		$this->log(ILogger::WARN, $message, $context);
157
	}
158
159
	/**
160
	 * Normal but significant events.
161
	 *
162
	 * @param string $message
163
	 * @param array $context
164
	 * @return void
165
	 */
166
	public function notice(string $message, array $context = []) {
167
		$this->log(ILogger::INFO, $message, $context);
168
	}
169
170
	/**
171
	 * Interesting events.
172
	 *
173
	 * Example: User logs in, SQL logs.
174
	 *
175
	 * @param string $message
176
	 * @param array $context
177
	 * @return void
178
	 */
179
	public function info(string $message, array $context = []) {
180
		$this->log(ILogger::INFO, $message, $context);
181
	}
182
183
	/**
184
	 * Detailed debug information.
185
	 *
186
	 * @param string $message
187
	 * @param array $context
188
	 * @return void
189
	 */
190
	public function debug(string $message, array $context = []) {
191
		$this->log(ILogger::DEBUG, $message, $context);
192
	}
193
194
195
	/**
196
	 * Logs with an arbitrary level.
197
	 *
198
	 * @param int $level
199
	 * @param string $message
200
	 * @param array $context
201
	 * @return void
202
	 */
203
	public function log(int $level, string $message, array $context = []) {
204
		$minLevel = $this->getLogLevel($context);
205
206
		array_walk($context, [$this->normalizer, 'format']);
207
208
		$app = $context['app'] ?? 'no app in context';
209
210
		// interpolate $message as defined in PSR-3
211
		$replace = [];
212
		foreach ($context as $key => $val) {
213
			$replace['{' . $key . '}'] = $val;
214
		}
215
		$message = strtr($message, $replace);
216
217
		if ($level >= $minLevel) {
218
			$this->writeLog($app, $message, $level);
219
		}
220
	}
221
222
	private function getLogLevel($context) {
223
		/**
224
		 * check for a special log condition - this enables an increased log on
225
		 * a per request/user base
226
		 */
227
		if ($this->logConditionSatisfied === null) {
228
			// default to false to just process this once per request
229
			$this->logConditionSatisfied = false;
230
			if (!empty($logCondition)) {
0 ignored issues
show
Bug introduced by
The variable $logCondition seems only to be defined at a later point. As such the call to empty() seems to always evaluate to true.

This check marks calls to isset(...) or empty(...) that are found before the variable itself is defined. These will always have the same result.

This is likely the result of code being shifted around. Consider removing these calls.

Loading history...
231
232
				// check for secret token in the request
233
				if (isset($logCondition['shared_secret'])) {
234
					$request = \OC::$server->getRequest();
235
236
					if ($request->getMethod() === 'PUT' &&
237
						strpos($request->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false &&
238
						strpos($request->getHeader('Content-Type'), 'application/json') === false) {
239
						$logSecretRequest = '';
240
					} else {
241
						$logSecretRequest = $request->getParam('log_secret', '');
242
					}
243
244
					// if token is found in the request change set the log condition to satisfied
245
					if ($request && hash_equals($logCondition['shared_secret'], $logSecretRequest)) {
246
						$this->logConditionSatisfied = true;
247
					}
248
				}
249
250
				// check for user
251
				if (isset($logCondition['users'])) {
252
					$user = \OC::$server->getUserSession()->getUser();
253
254
					// if the user matches set the log condition to satisfied
255
					if ($user !== null && in_array($user->getUID(), $logCondition['users'], true)) {
256
						$this->logConditionSatisfied = true;
257
					}
258
				}
259
			}
260
		}
261
262
		// if log condition is satisfied change the required log level to DEBUG
263
		if ($this->logConditionSatisfied) {
264
			return ILogger::DEBUG;
265
		}
266
267
		if (isset($context['app'])) {
268
			$logCondition = $this->config->getValue('log.condition', []);
269
			$app = $context['app'];
270
271
			/**
272
			 * check log condition based on the context of each log message
273
			 * once this is met -> change the required log level to debug
274
			 */
275
			if (!empty($logCondition)
276
				&& isset($logCondition['apps'])
277
				&& in_array($app, $logCondition['apps'], true)) {
278
				return ILogger::DEBUG;
279
			}
280
		}
281
282
		return min($this->config->getValue('loglevel', ILogger::WARN), ILogger::FATAL);
283
	}
284
285
	/**
286
	 * Logs an exception very detailed
287
	 *
288
	 * @param \Exception|\Throwable $exception
289
	 * @param array $context
290
	 * @return void
291
	 * @since 8.2.0
292
	 */
293
	public function logException(\Throwable $exception, array $context = []) {
294
		$app = $context['app'] ?? 'no app in context';
295
		$level = $context['level'] ?? ILogger::ERROR;
296
297
		$serializer = new ExceptionSerializer();
298
		$data = $serializer->serializeException($exception);
299
		$data['CustomMessage'] = $context['message'] ?? '--';
300
301
		$minLevel = $this->getLogLevel($context);
302
303
		array_walk($context, [$this->normalizer, 'format']);
304
305
		if ($level >= $minLevel) {
306
			if (!$this->logger instanceof IFileBased) {
307
				$data = json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR);
308
			}
309
			$this->writeLog($app, $data, $level);
310
		}
311
312
		$context['level'] = $level;
313
		if (!is_null($this->crashReporters)) {
314
			$this->crashReporters->delegateReport($exception, $context);
315
		}
316
	}
317
318
	/**
319
	 * @param string $app
320
	 * @param string|array $entry
321
	 * @param int $level
322
	 */
323
	protected function writeLog(string $app, $entry, int $level) {
324
		$this->logger->write($app, $entry, $level);
325
	}
326
327
	public function getLogPath():string {
328
		if($this->logger instanceof IFileBased) {
329
			return $this->logger->getLogFilePath();
330
		}
331
		throw new \RuntimeException('Log implementation has no path');
332
	}
333
}
334