Completed
Push — master ( a56ec1...2aa108 )
by Morris
44:44 queued 16:07
created

Log::critical()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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