Test Failed
Push — master ( 9ad798...8afcbf )
by
unknown
11:57
created

GLogger::configure()   C

Complexity

Conditions 15
Paths 76

Size

Total Lines 63
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 36
nc 76
nop 1
dl 0
loc 63
rs 5.9166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 * SPDX-License-Identifier: AGPL-3.0-only
4
 * SPDX-FileCopyrightText: Copyright 2016 - 2018 Kopano b.v.
5
 * SPDX-FileCopyrightText: Copyright 2020 grommunio GmbH
6
 *
7
 * A wrapper for Monolog Logger.
8
 */
9
10
namespace grommunio\DAV;
11
12
use grommunio\DAV\MonologWrapper as Logger;
13
use Monolog\Formatter\LineFormatter;
14
use Monolog\Handler\StreamHandler;
15
use Monolog\Handler\ErrorLogHandler;
16
use Monolog\Processor\ProcessIdProcessor;
17
18
/**
19
 * GLogger: wraps the monolog Logger
20
 *
21
 * If you want other methods of Logger please add a wrapper method to this class.
22
 */
23
class GLogger {
24
	protected static $listOfLoggers = [];
25
	protected static $parentLogger;
26
	protected $logger;
27
28
	/**
29
	 * Constructor.
30
	 *
31
	 * @param mixed $name
32
	 */
33
	public function __construct($name) {
34
        $this->logger = self::$parentLogger->withName($name);
35
36
		// keep an output puffer in case we do debug logging
37
		if ($this->logger->isHandling(Logger::DEBUG)) {
38
			ob_start();
39
		}
40
41
		// let GLogger handle error messages
42
		set_error_handler('\\grommunio\\DAV\\GLogger::ErrorHandler');
43
	}
44
45
	/**
46
	 * Configures parent monolog Logger.
47
	 *
48
	 * This method needs to be called before the first logging event has
49
	 * occurred.
50
	 *
51
	 * @param array|string              $configuration either a path to the configuration
52
	 *                                                 file, or a configuration array
53
	 */
54
	public static function configure($configuration = null) {
55
56
        // Load configuration from ini-file if a file path (string) is given
57
        if (is_string($configuration)) {
58
            $configuration = parse_ini_file($configuration);
59
            if (!is_array($configuration))
0 ignored issues
show
introduced by
The condition is_array($configuration) is always true.
Loading history...
60
                throw new \Exception('Invalid GLogger configuration file');
61
        } elseif (!is_array($configuration)) {
62
            throw new \Exception('GLogger configuration should be either a string with path to the configuration file, or a configuration array');
63
        }
64
65
        // Log level
66
        if (!isset($configuration['level']))
67
            $configuration['level'] = 'INFO';
68
        elseif (!is_string($configuration['level'])) {
69
            throw new \Exception('GLogger configuration parameter "level" is not a string');
70
        }
71
72
        $configuration['level'] = strtoupper($configuration['level']);
73
        $allLogLevels = Logger::getLevels();
74
75
        if (!isset($allLogLevels[$configuration['level']]))
76
            throw new \Exception('GLogger configuration parameter "level" is not known');
77
78
        $logLevel = $allLogLevels[$configuration['level']];
79
80
        // Parent logger class
81
        static::$parentLogger = new Logger('');
82
83
        // Without configuration parameter 'file' all log messages will go to error_log()
84
        if (isset($configuration['file'])) {
85
            if (!is_string($configuration['file']))
86
                throw new \Exception('GLogger configuration parameter "file" is not a string');
87
88
            $stream = new StreamHandler($configuration['file'], $logLevel);
89
        } else {
90
            $stream = new ErrorLogHandler(ErrorLogHandler::OPERATING_SYSTEM, $logLevel);
91
        }
92
93
        // Log messages formatting
94
        $lineFormat = null;
95
96
        if (isset($configuration['lineFormat'])
97
            && is_string($configuration['lineFormat']))
98
            $lineFormat = stripcslashes($configuration['lineFormat']); // stripcslashes to recognize "\n"
99
100
        $timeFormat = null;
101
102
        if (isset($configuration['timeFormat'])
103
            && is_string($configuration['timeFormat']))
104
            $timeFormat = $configuration['timeFormat'];
105
106
        if ($lineFormat
107
            || $timeFormat) {
108
109
            $formatter = new LineFormatter($lineFormat, $timeFormat, true, true);
110
            $stream->setFormatter($formatter);
111
        }
112
113
        static::$parentLogger->pushHandler($stream);
114
115
        // Add processor id (pid) to log messages
116
        static::$parentLogger->pushProcessor(new ProcessIdProcessor());
117
	}
118
119
	/**
120
	 * Destroy configurations for logger definitions.
121
	 */
122
	public function resetConfiguration() {
123
        if (static::$parentLogger) {
124
            static::$parentLogger->reset();
125
            static::$parentLogger = null;
126
        }
127
	}
128
129
	public function getGPSR3Logger() {
130
        return $this->logger;
131
    }
132
133
	/**
134
	 * Returns a GLogger by name. If it does not exist, it will be created.
135
	 *
136
	 * @param string $name  The logger name
137
	 * @param mixed  $class
138
	 *
139
	 * @return Logger
140
	 */
141
	public static function GetLogger($class) {
142
		if (!isset(static::$listOfLoggers[$class])) {
143
			static::$listOfLoggers[$class] = new GLogger(static::GetClassnameOnly($class));
144
		}
145
146
		return static::$listOfLoggers[$class];
147
	}
148
149
	/**
150
	 * Cuts of the namespace and returns just the classname.
151
	 *
152
	 * @param string $namespaceWithClass
153
	 *
154
	 * @return string
155
	 */
156
	protected static function GetClassnameOnly($namespaceWithClass) {
157
		if (strpos($namespaceWithClass, '\\') == false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing strpos($namespaceWithClass, '\') of type integer to the boolean false. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
158
			return $namespaceWithClass;
159
		}
160
161
		return substr(strrchr($namespaceWithClass, '\\'), 1);
162
	}
163
164
	/**
165
	 * Logs the incoming data (headers + body) to debug.
166
	 */
167
	public function LogIncoming(\Sabre\HTTP\RequestInterface $request) {
168
		// only do any of this is we are looking for debug messages
169
		if ($this->logger->isHandling(Logger::DEBUG)) {
170
			$inputHeader = $request->getMethod() . ' ' . $request->getUrl() . ' HTTP/' . $request->getHTTPVersion() . "\r\n";
171
			foreach ($request->getHeaders() as $key => $value) {
172
				if ($key === 'Authorization') {
173
					list($value) = explode(' ', implode(',', $value), 2);
174
					$value = [$value . ' REDACTED'];
175
				}
176
				$inputHeader .= $key . ": " . implode(',', $value) . "\r\n";
177
			}
178
			// reopen the input so we can read it (again)
179
			$inputBody = stream_get_contents(fopen('php://input', 'r'));
180
			// format incoming xml to be better human readable
181
			if (stripos($inputBody, '<?xml') === 0) {
182
				$dom = new \DOMDocument('1.0', 'utf-8');
183
				$dom->preserveWhiteSpace = false;
184
				$dom->formatOutput = true;
185
				$dom->recover = true;
186
				$dom->loadXML($inputBody);
187
				$inputBody = $dom->saveXML();
188
			}
189
			// log incoming data
190
			$this->debug("INPUT\n" . $inputHeader . "\n" . $inputBody);
191
		}
192
	}
193
194
	/**
195
	 * Logs the outgoing data (headers + body) to debug.
196
	 */
197
	public function LogOutgoing(\Sabre\HTTP\ResponseInterface $response) {
198
		// only do any of this is we are looking for debug messages
199
		if ($this->logger->isHandling(Logger::DEBUG)) {
200
			$output = 'HTTP/' . $response->getHttpVersion() . ' ' . $response->getStatus() . ' ' . $response->getStatusText() . "\n";
201
			foreach ($response->getHeaders() as $key => $value) {
202
				$output .= $key . ": " . implode(',', $value) . "\n";
203
			}
204
			$outputBody = ob_get_contents();
205
			if (stripos($outputBody, '<?xml') === 0) {
206
				$dom = new \DOMDocument('1.0', 'utf-8');
207
				$dom->preserveWhiteSpace = false;
208
				$dom->formatOutput = true;
209
				$dom->recover = true;
210
				$dom->loadXML($outputBody);
211
				$outputBody = $dom->saveXML();
212
			}
213
			$this->debug("OUTPUT:\n" . $output . "\n" . $outputBody);
214
215
			ob_end_flush();
216
		}
217
	}
218
219
	/**
220
	 * Runs the arguments through sprintf() and sends it to the logger.
221
	 *
222
	 * @param \LoggerLevel $level
0 ignored issues
show
Bug introduced by
The type LoggerLevel was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
223
	 * @param array        $args
224
	 * @param string       $suffix an optional suffix that is appended to the message
225
	 */
226
	protected function writeLog($level, $args, $suffix = '') {
227
		$outArgs = [];
228
		foreach ($args as $arg) {
229
			if (is_array($arg)) {
230
				$outArgs[] = print_r($arg, true);
231
			}
232
			$outArgs[] = $arg;
233
		}
234
		// Call sprintf() with the arguments only if there are format parameters because
235
		// otherwise sprintf will complain about too few arguments.
236
		// This also prevents throwing errors if there are %-chars in the $outArgs.
237
		$message = (count($outArgs) > 1) ? call_user_func_array('sprintf', $outArgs) : $outArgs[0];
238
		// prepend class+method and log the message
239
		$this->logger->log($level, $this->getCaller(2) . $message . $suffix);
240
	}
241
242
	/**
243
	 * Verifies if the dynamic amount of logging arguments matches the amount of variables (%) in the message.
244
	 *
245
	 * @param array $arguments
246
	 *
247
	 * @return bool
248
	 */
249
	protected function verifyLogSyntax($arguments) {
250
		$count = count($arguments);
251
		$quoted_procent = substr_count($arguments[0], "%%");
252
		$t = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
253
254
		if ($count == 0) {
255
			$this->logger->error(sprintf("No arguments in %s->%s() logging to '%s' in %s:%d", static::GetClassnameOnly($t[2]['class']), $t[2]['function'], $t[1]['function'], $t[1]['file'], $t[1]['line']));
256
257
			return false;
258
		}
259
		// Only check formatting if there are format parameters. Otherwise there will be
260
		// an error log if the $arguments[0] contain %-chars.
261
		if (($count > 1) && ((substr_count($arguments[0], "%") - $quoted_procent * 2) !== $count - 1)) {
262
			$this->logger->error(sprintf("Wrong number of arguments in %s->%s() logging to '%s' in %s:%d", static::GetClassnameOnly($t[2]['class']), $t[2]['function'], $t[1]['function'], $t[1]['file'], $t[1]['line']));
263
264
			return false;
265
		}
266
267
		return true;
268
	}
269
270
	/**
271
	 * Returns a string in the form of "Class->Method(): " or "file:line" if requested.
272
	 *
273
	 * @param number $level    the level you want the info from, default 1
274
	 * @param bool   $fileline returns "file:line" if set to true
275
	 *
276
	 * @return string
277
	 */
278
	protected function getCaller($level = 1, $fileline = false) {
279
		$wlevel = $level + 1;
280
		$t = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $wlevel + 1);
0 ignored issues
show
Bug introduced by
$wlevel + 1 of type double is incompatible with the type integer expected by parameter $limit of debug_backtrace(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

280
		$t = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, /** @scrutinizer ignore-type */ $wlevel + 1);
Loading history...
281
		if (isset($t[$wlevel]['function'])) {
282
			if ($fileline) {
283
				return $t[$wlevel]['file'] . ":" . $t[$wlevel]['line'];
284
			}
285
286
			return $this->GetClassnameOnly($t[$wlevel]['class']) . '->' . $t[$wlevel]['function'] . '(): ';
287
		}
288
289
		return '';
290
	}
291
292
	/**
293
	 * Format bytes to a more human readable value.
294
	 *
295
	 * @param int $bytes
296
	 * @param int $precision
297
	 *
298
	 * @return string
299
	 */
300
	public function FormatBytes($bytes, $precision = 2) {
301
		if ($bytes <= 0) {
302
			return '0 B';
303
		}
304
305
		$units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB'];
306
		$base = log($bytes, 1024);
307
		$fBase = floor($base);
308
		$pow = pow(1024, $base - $fBase);
309
310
		return sprintf("%.{$precision}f %s", $pow, $units[$fBase]);
311
	}
312
313
	/**
314
	 * The GrommunioDav error handler.
315
	 *
316
	 * @param int    $errno
317
	 * @param string $errstr
318
	 * @param string $errfile
319
	 * @param int    $errline
320
	 * @param mixed  $errcontext
321
	 */
322
	public static function ErrorHandler($errno, $errstr, $errfile, $errline, $errcontext = []) {
0 ignored issues
show
Unused Code introduced by
The parameter $errcontext is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

322
	public static function ErrorHandler($errno, $errstr, $errfile, $errline, /** @scrutinizer ignore-unused */ $errcontext = []) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
323
		if (defined('LOG_ERROR_MASK')) {
324
			$errno &= LOG_ERROR_MASK;
0 ignored issues
show
Bug introduced by
The constant grommunio\DAV\LOG_ERROR_MASK was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
325
		}
326
327
		switch ($errno) {
328
			case 0:
329
				// logging disabled by LOG_ERROR_MASK
330
				break;
331
332
			case E_DEPRECATED:
333
				// do not handle this message
334
				break;
335
336
			case E_NOTICE:
337
			case E_WARNING:
338
				$logger = \Logger::getLogger('error');
0 ignored issues
show
Bug introduced by
The type Logger was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
339
				$logger->warn("{$errfile}:{$errline} {$errstr} ({$errno})");
340
				break;
341
342
			default:
343
				$bt = debug_backtrace();
344
				$logger = \Logger::getLogger('error');
345
				$logger->error("trace error: {$errfile}:{$errline} {$errstr} ({$errno}) - backtrace: " . (count($bt) - 1) . " steps");
346
				for ($i = 1, $bt_length = count($bt); $i < $bt_length; ++$i) {
347
					$file = $line = "unknown";
348
					if (isset($bt[$i]['file'])) {
349
						$file = $bt[$i]['file'];
350
					}
351
					if (isset($bt[$i]['line'])) {
352
						$line = $bt[$i]['line'];
353
					}
354
					$logger->error("trace: {$i}:" . $file . ":" . $line . " - " . ((isset($bt[$i]['class'])) ? $bt[$i]['class'] . $bt[$i]['type'] : "") . $bt[$i]['function'] . "()");
355
				}
356
				break;
357
		}
358
	}
359
360
	/**
361
	 * Wrapper of the \Logger class.
362
	 */
363
364
	/**
365
	 * Log a message object with the TRACE level.
366
	 * It has the same footprint as sprintf(), but arguments are only processed
367
	 * if the loglevel is activated.
368
	 *
369
	 * @param mixed $message message
370
	 * @param mixed ...params
371
	 */
372
	public function trace() {
373
		if (DEVELOPER_MODE) {
374
			if (!$this->verifyLogSyntax(func_get_args())) {
375
				return;
376
			}
377
		}
378
		if ($this->logger->isHandling(Logger::TRACE)) {
379
			$this->writeLog(Logger::TRACE, func_get_args());
0 ignored issues
show
Bug introduced by
grommunio\DAV\MonologWrapper::TRACE of type integer is incompatible with the type LoggerLevel expected by parameter $level of grommunio\DAV\GLogger::writeLog(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

379
			$this->writeLog(/** @scrutinizer ignore-type */ Logger::TRACE, func_get_args());
Loading history...
380
		}
381
	}
382
383
	/**
384
	 * Log a message object with the DEBUG level.
385
	 * It has the same footprint as sprintf(), but arguments are only processed
386
	 * if the loglevel is activated.
387
	 *
388
	 * @param mixed $message message
389
	 * @param mixed ...params
390
	 */
391
	public function debug() {
392
		if (DEVELOPER_MODE) {
393
			if (!$this->verifyLogSyntax(func_get_args())) {
394
				return;
395
			}
396
		}
397
		if ($this->logger->isHandling(Logger::DEBUG)) {
398
			$this->writeLog(Logger::DEBUG, func_get_args());
0 ignored issues
show
Bug introduced by
grommunio\DAV\MonologWrapper::DEBUG of type integer is incompatible with the type LoggerLevel expected by parameter $level of grommunio\DAV\GLogger::writeLog(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

398
			$this->writeLog(/** @scrutinizer ignore-type */ Logger::DEBUG, func_get_args());
Loading history...
399
		}
400
	}
401
402
	/**
403
	 * Log a message object with the INFO level.
404
	 * It has the same footprint as sprintf(), but arguments are only processed
405
	 * if the loglevel is activated.
406
	 *
407
	 * @param mixed $message message
408
	 * @param mixed ...params
409
	 */
410
	public function info() {
411
		if (DEVELOPER_MODE) {
412
			if (!$this->verifyLogSyntax(func_get_args())) {
413
				return;
414
			}
415
		}
416
		if ($this->logger->isHandling(Logger::INFO)) {
417
			$this->writeLog(Logger::INFO, func_get_args());
0 ignored issues
show
Bug introduced by
grommunio\DAV\MonologWrapper::INFO of type integer is incompatible with the type LoggerLevel expected by parameter $level of grommunio\DAV\GLogger::writeLog(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

417
			$this->writeLog(/** @scrutinizer ignore-type */ Logger::INFO, func_get_args());
Loading history...
418
		}
419
	}
420
421
	/**
422
	 * Log a message object with the WARN level.
423
	 * It has the same footprint as sprintf(), but arguments are only processed
424
	 * if the loglevel is activated.
425
	 *
426
	 * @param mixed $message message
427
	 * @param mixed ...params
428
	 */
429
	public function warn() {
430
		if (DEVELOPER_MODE) {
431
			if (!$this->verifyLogSyntax(func_get_args())) {
432
				return;
433
			}
434
		}
435
		if ($this->logger->isHandling(Logger::WARNING)) {
436
			$this->writeLog(Logger::WARNING, func_get_args(), ' - ' . $this->getCaller(1, true));
0 ignored issues
show
Bug introduced by
grommunio\DAV\MonologWrapper::WARNING of type integer is incompatible with the type LoggerLevel expected by parameter $level of grommunio\DAV\GLogger::writeLog(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

436
			$this->writeLog(/** @scrutinizer ignore-type */ Logger::WARNING, func_get_args(), ' - ' . $this->getCaller(1, true));
Loading history...
437
		}
438
	}
439
440
	/**
441
	 * Log a message object with the ERROR level.
442
	 * It has the same footprint as sprintf(), but arguments are only processed
443
	 * if the loglevel is activated.
444
	 *
445
	 * @param mixed $message message
446
	 * @param mixed ...params
447
	 */
448
	public function error() {
449
		if (DEVELOPER_MODE) {
450
			if (!$this->verifyLogSyntax(func_get_args())) {
451
				return;
452
			}
453
		}
454
		if ($this->logger->isHandling(Logger::ERROR)) {
455
			$this->writeLog(Logger::ERROR, func_get_args(), ' - ' . $this->getCaller(1, true));
0 ignored issues
show
Bug introduced by
grommunio\DAV\MonologWrapper::ERROR of type integer is incompatible with the type LoggerLevel expected by parameter $level of grommunio\DAV\GLogger::writeLog(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

455
			$this->writeLog(/** @scrutinizer ignore-type */ Logger::ERROR, func_get_args(), ' - ' . $this->getCaller(1, true));
Loading history...
456
		}
457
	}
458
459
	/**
460
	 * Log a message object with the FATAL level.
461
	 * It has the same footprint as sprintf(), but arguments are only processed
462
	 * if the loglevel is activated.
463
	 *
464
	 * @param mixed $message message
465
	 * @param mixed ...params
466
	 */
467
	public function fatal() {
468
		if (DEVELOPER_MODE) {
469
			if (!$this->verifyLogSyntax(func_get_args())) {
470
				return;
471
			}
472
		}
473
		if ($this->logger->isHandling(Logger::CRITICAL)) {
474
			$this->writeLog(Logger::CRITICAL, func_get_args(), ' - ' . $this->getCaller(1, true));
0 ignored issues
show
Bug introduced by
grommunio\DAV\MonologWrapper::CRITICAL of type integer is incompatible with the type LoggerLevel expected by parameter $level of grommunio\DAV\GLogger::writeLog(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

474
			$this->writeLog(/** @scrutinizer ignore-type */ Logger::CRITICAL, func_get_args(), ' - ' . $this->getCaller(1, true));
Loading history...
475
		}
476
	}
477
}
478