Completed
Push — master ( 42b7df...991790 )
by Robin
41s
created

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

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
334
				return \OC\Log\Errorlog::class;
335
			case 'syslog':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
336
				return \OC\Log\Syslog::class;
337
			case 'file':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
338
				return \OC\Log\File::class;
339
340
			// Backwards compatibility for old and fallback for unknown log types
341
			case 'owncloud':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
342
			case 'nextcloud':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
343
			default:
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
344
				return \OC\Log\File::class;
345
		}
346
	}
347
}
348