Passed
Pull Request — master (#583)
by Michael
14:01
created

Logger   F

Complexity

Total Complexity 62

Size/Duplication

Total Lines 446
Duplicated Lines 0 %

Test Coverage

Coverage 66.45%

Importance

Changes 0
Metric Value
eloc 126
dl 0
loc 446
ccs 101
cts 152
cp 0.6645
rs 3.44
c 0
b 0
f 0
wmc 62

21 Methods

Rating   Name   Duplication   Size   Complexity  
D handleError() 0 69 23
A getInstance() 0 13 2
A log() 0 8 5
A handleException() 0 10 3
A info() 0 3 1
A quiet() 0 8 6
A deprecatedMessage() 0 4 1
A __set() 0 6 3
A reportFatalError() 0 13 3
A debug() 0 3 1
A isThrowable() 0 4 2
A addLogger() 0 5 3
A sanitizePath() 0 22 1
A alert() 0 3 1
A notice() 0 3 1
A __get() 0 3 1
A __call() 0 3 1
A error() 0 3 1
A emergency() 0 3 1
A warning() 0 3 1
A critical() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Logger often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Logger, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 You may not change or alter any portion of this comment or credits
4
 of supporting developers from this source code or any supporting source code
5
 which is considered copyrighted (c) material of the original comment or credit authors.
6
7
 This program is distributed in the hope that it will be useful,
8
 but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
*/
11
12
namespace Xoops\Core;
13
14
use Psr\Log\LogLevel;
15
use Psr\Log\LoggerInterface;
16
17
/**
18
 * Xoops\Core\Logger - dispatch log requests to any registered loggers.
19
 *
20
 * No logging is done in this class, but any logger, implemented as a
21
 * module or extension, can register as a logger using the addLogger()
22
 * method. Multiple loggers can be registered, and each will be
23
 * invoked in turn for each log() call.
24
 *
25
 * Such loggers are expected to implement the PSR-3 LoggerInterface.
26
 * In addition, any logger that generates output as part of the XOOPS
27
 * delivered page should implement the quiet() method, to disable output.
28
 *
29
 * Loggers are managed this way so that any routine may easily add a
30
 * log entry without needing to know any details of the implementation.
31
 *
32
 * Not all events are published through this mechanism, only specific requests
33
 * to log() or related methods. Individual loggers may listen for events (i.e.
34
 * preloads) or other sources and gain access to detailed debugging information.
35
 *
36
 * @category  Xoops\Core\Logger
37
 * @package   Logger
38
 * @author    Richard Griffith <[email protected]>
39
 * @copyright 2013 XOOPS Project (http://xoops.org)
40
 * @license   GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
41
 * @version   Release: 2.6.0
42
 * @link      http://xoops.org
43
 * @since     2.6.0
44
 */
45
class Logger implements LoggerInterface
46
{
47
    /**
48
     * @var LoggerInterface[] chain of PSR-3 compatible loggers to call
49
     */
50
    private $loggers = array();
51
52
    /**
53
     * @var boolean do we have active loggers?
54
     */
55
    private $logging_active = false;
56
57
    /**
58
     * @var boolean just to prevent fatal legacy errors. Does nothing. Stop it!
59
     */
60
    //public $activated = false;
61
62
    /**
63
     * Get the Xoops\Core\Logger instance
64
     *
65
     * @return Logger object
66
     */
67 21
    public static function getInstance()
68
    {
69 21
        static $instance;
70 21
        if (!isset($instance)) {
71
            $class = __CLASS__;
72
            $instance = new $class();
73
            // Always catch errors, for security reasons
74
            set_error_handler(array($instance, 'handleError'));
75
            // grab any uncaught exception
76
            set_exception_handler(array($instance, 'handleException'));
77
        }
78
79 21
        return $instance;
80
    }
81
82
    /**
83
     * Error handling callback.
84
     *
85
     * This will
86
     *
87
     * @param integer $errorNumber error number
88
     * @param string  $errorString error message
89
     * @param string  $errorFile   file
90
     * @param integer $errorLine   line number
91
     *
92
     * @return void
93
     */
94 91
    public function handleError($errorNumber, $errorString, $errorFile, $errorLine)
95
    {
96 91
        if ($this->logging_active && ($errorNumber & error_reporting())) {
97
98
            // if an error occurs before a locale is established,
99
            // we still need messages, so check and deal with it
100
101 5
            $msg = ': ' . sprintf(
102 5
                (class_exists('\XoopsLocale', false) ? \XoopsLocale::EF_LOGGER_FILELINE : "%s in file %s line %s"),
103 5
                $this->sanitizePath($errorString),
104 5
                $this->sanitizePath($errorFile),
105 5
                $errorLine
106
            );
107
108
            switch ($errorNumber) {
109 5
                case E_USER_NOTICE:
110 1
                    $msg = (class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_ERROR : '*Error:') . $msg;
111 1
                    $this->log(LogLevel::NOTICE, $msg);
112 1
                    break;
113 4
                case E_NOTICE:
114 1
                    $msg = (class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_NOTICE : '*Notice:') . $msg;
115 1
                    $this->log(LogLevel::NOTICE, $msg);
116 1
                    break;
117 3
                case E_WARNING:
118 1
                    $msg = (class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_WARNING : '*Warning:') . $msg;
119 1
                    $this->log(LogLevel::WARNING, $msg);
120 1
                    break;
121 2
                case E_STRICT:
122 1
                    $msg = (class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_STRICT : '*Strict:') . $msg;
123 1
                    $this->log(LogLevel::WARNING, $msg);
124 1
                    break;
125 1
                case E_USER_ERROR:
126
                    $msg = (class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_ERROR : '*Error:') . $msg;
127
                    @$this->log(LogLevel::CRITICAL, $msg);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->log(Psr\Log\LogLevel::CRITICAL, $msg) targeting Xoops\Core\Logger::log() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Security Best Practice introduced by
It seems like you do not handle an error condition for log(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

127
                    /** @scrutinizer ignore-unhandled */ @$this->log(LogLevel::CRITICAL, $msg);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
128
                    break;
129
                default:
130 1
                    $msg = (class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_UNKNOWN : '*Unknown:') . $msg;
131 1
                    $this->log(LogLevel::ERROR, $msg);
132 1
                    break;
133
            }
134
        }
135
136 91
        if ($errorNumber == E_USER_ERROR) {
137
            $trace = true;
138
            if (substr($errorString, 0, '8') === 'notrace:') {
139
                $trace = false;
140
                $errorString = substr($errorString, 8);
141
            }
142
            $this->reportFatalError($errorString);
143
            if ($trace) {
144
                $trace = debug_backtrace();
145
                array_shift($trace);
146
                if ('cli' === php_sapi_name()) {
147
                    foreach ($trace as $step) {
148
                        if (isset($step['file'])) {
149
                            fprintf(STDERR, "%s (%d)\n", $this->sanitizePath($step['file']), $step['line']);
150
                        }
151
                    }
152
                } else {
153
                    echo "<div style='color:#f0f0f0;background-color:#f0f0f0'>" . _XOOPS_FATAL_BACKTRACE . ":<br />";
154
                    foreach ($trace as $step) {
155
                        if (isset($step['file'])) {
156
                            printf("%s (%d)\n<br />", $this->sanitizePath($step['file']), $step['line']);
157
                        }
158
                    }
159
                    echo '</div>';
160
                }
161
            }
162
            exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
163
        }
164 91
    }
165
166
    /**
167
     * Exception handling callback.
168
     *
169
     * @param \Exception|\Throwable $e uncaught Exception or Error
170
     *
171
     * @return void
172
     */
173
    public function handleException($e)
174
    {
175
        $xoops = \Xoops::getInstance();
176
        if ($this->isThrowable($e)) {
177
            if ($xoops->isAdmin()) {
178
                $msg = $e->xdebug_message;
0 ignored issues
show
Bug introduced by
The property xdebug_message does not exist on Exception. Did you mean message?
Loading history...
179
            } else {
180
            $msg = $e->getMessage();
181
            }
182
            $this->reportFatalError($msg);
183
        }
184
    }
185
186
    /**
187
     * Determine if an object implements Throwable (or is an Exception that would under PHP 7.)
188
     *
189
     * @param mixed $e Expected to be an object related to Exception or Throwable
190
     *
191
     * @return bool true if related to Throwable or Exception, otherwise false
192
     */
193
    protected function isThrowable($e)
194
    {
195
        $type = interface_exists('\Throwable', false) ? '\Throwable' : '\Exception';
196
        return $e instanceof $type;
197
    }
198
199
    /**
200
     * Announce fatal error, attempt to log
201
     *
202
     * @param string $msg error message to report
203
     *
204
     * @return void
205
     */
206
    private function reportFatalError($msg)
207
    {
208
        $msg=$this->sanitizePath($msg);
209
        if ('cli' === php_sapi_name()) {
210
            fprintf(STDERR, "\nError : %s\n", $msg);
211
        } else {
212
            if (defined('_XOOPS_FATAL_MESSAGE')) {
213
                printf(_XOOPS_FATAL_MESSAGE, XOOPS_URL, $msg);
214
            } else {
215
                printf("\nError : %s\n", $msg);
216
            }
217
        }
218
        @$this->log(LogLevel::CRITICAL, $msg);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->log(Psr\Log\LogLevel::CRITICAL, $msg) targeting Xoops\Core\Logger::log() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Security Best Practice introduced by
It seems like you do not handle an error condition for log(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

218
        /** @scrutinizer ignore-unhandled */ @$this->log(LogLevel::CRITICAL, $msg);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
219
    }
220
221
    /**
222
     * clean a path to remove sensitive details
223
     *
224
     * @param string $message text to sanitize
225
     *
226
     * @return string sanitized message
227
     */
228 23
    public function sanitizePath($message)
229
    {
230
        $cleaners = [
231 23
            ['\\', '/',],
232 23
            [\XoopsBaseConfig::get('var-path'), 'VAR',],
233 23
            [str_replace('\\', '/', realpath(\XoopsBaseConfig::get('var-path'))), 'VAR',],
234 23
            [\XoopsBaseConfig::get('lib-path'), 'LIB',],
235 23
            [str_replace('\\', '/', realpath(\XoopsBaseConfig::get('lib-path'))), 'LIB',],
236 23
            [\XoopsBaseConfig::get('root-path'), 'ROOT',],
237 23
            [str_replace('\\', '/', realpath(\XoopsBaseConfig::get('root-path'))), 'ROOT',],
238 23
            [\XoopsBaseConfig::get('db-name') . '.', '',],
239 23
            [\XoopsBaseConfig::get('db-name'), '',],
240 23
            [\XoopsBaseConfig::get('db-prefix') . '_', '',],
241 23
            [\XoopsBaseConfig::get('db-user'), '***',],
242 23
            [\XoopsBaseConfig::get('db-pass'), '***',],
243
        ];
244 23
        $stringsToClean = array_column($cleaners, 0);
245 23
        $replacementStings = array_column($cleaners, 1);
246
247 23
        $message = str_replace($stringsToClean, $replacementStings, $message);
248
249 23
        return $message;
250
    }
251
252
    /**
253
     * add a PSR-3 compatible logger to the chain
254
     *
255
     * @param object $logger a PSR-3 compatible logger object
256
     *
257
     * @return void
258
     */
259 17
    public function addLogger($logger)
260
    {
261 17
        if (is_object($logger) && method_exists($logger, 'log')) {
262 17
                $this->loggers[] = $logger;
263 17
                $this->logging_active = true;
264
        }
265 17
    }
266
267
    /**
268
     * System is unusable.
269
     *
270
     * @param string $message message
271
     * @param array  $context array of context data for this log entry
272
     *
273
     * @return void
274
     */
275 1
    public function emergency($message, array $context = array())
276
    {
277 1
        $this->log(LogLevel::EMERGENCY, $message, $context);
278 1
    }
279
280
    /**
281
     * Action must be taken immediately.
282
     *
283
     * Example: Entire website down, database unavailable, etc. This should
284
     * trigger the SMS alerts and wake you up.
285
     *
286
     * @param string $message message
287
     * @param array  $context array of context data for this log entry
288
     *
289
     * @return void
290
     */
291 1
    public function alert($message, array $context = array())
292
    {
293 1
        $this->log(LogLevel::ALERT, $message, $context);
294 1
    }
295
296
    /**
297
     * Critical conditions.
298
     *
299
     * Example: Application component unavailable, unexpected exception.
300
     *
301
     * @param string $message message
302
     * @param array  $context array of context data for this log entry
303
     *
304
     * @return void
305
     */
306 1
    public function critical($message, array $context = array())
307
    {
308 1
        $this->log(LogLevel::CRITICAL, $message, $context);
309 1
    }
310
311
    /**
312
     * Runtime errors that do not require immediate action but should typically
313
     * be logged and monitored.
314
     *
315
     * @param string $message message
316
     * @param array  $context array of context data for this log entry
317
     *
318
     * @return void
319
     */
320 1
    public function error($message, array $context = array())
321
    {
322 1
        $this->log(LogLevel::ERROR, $message, $context);
323 1
    }
324
325
    /**
326
     * Exceptional occurrences that are not errors.
327
     *
328
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
329
     * that are not necessarily wrong.
330
     *
331
     * @param string $message message
332
     * @param array  $context array of context data for this log entry
333
     *
334
     * @return void
335
     */
336 1
    public function warning($message, array $context = array())
337
    {
338 1
        $this->log(LogLevel::WARNING, $message, $context);
339 1
    }
340
341
    /**
342
     * Normal but significant events.
343
     *
344
     * @param string $message message
345
     * @param array  $context array of context data for this log entry
346
     *
347
     * @return void
348
     */
349 1
    public function notice($message, array $context = array())
350
    {
351 1
        $this->log(LogLevel::NOTICE, $message, $context);
352 1
    }
353
354
    /**
355
     * Interesting events.
356
     *
357
     * Example: User logs in, SQL logs.
358
     *
359
     * @param string $message message
360
     * @param array  $context array of context data for this log entry
361
     *
362
     * @return void
363
     */
364 1
    public function info($message, array $context = array())
365
    {
366 1
        $this->log(LogLevel::INFO, $message, $context);
367 1
    }
368
369
    /**
370
     * Detailed debug information.
371
     *
372
     * @param string $message message
373
     * @param array  $context array of context data for this log entry
374
     *
375
     * @return void
376
     */
377 1
    public function debug($message, array $context = array())
378
    {
379 1
        $this->log(LogLevel::DEBUG, $message, $context);
380 1
    }
381
382
    /**
383
     * Logs with an arbitrary level.
384
     *
385
     * @param mixed  $level   PSR-3 LogLevel constant
386
     * @param string $message message
387
     * @param array  $context array of context data for this log entry
388
     *
389
     * @return void
390
     */
391 14
    public function log($level, $message, array $context = array())
392
    {
393 14
        if (!empty($this->loggers)) {
394 13
            foreach ($this->loggers as $logger) {
395 13
                if (is_object($logger)) {
396
                    try {
397 13
                        $logger->log($level, $message, $context);
398 13
                    } catch (\Exception $e) {
399
                        // just ignore, as we can't do anything, not even log it.
400
                    }
401
                }
402
            }
403
        }
404 14
    }
405
406
    /**
407
     * quiet - turn off output if output is rendered in XOOPS page output.
408
     * This is intended to assist ajax code that may fail with any extra
409
     * content the logger may introduce.
410
     *
411
     * It should have no effect on loggers using other methods, such a write
412
     * to file.
413
     *
414
     * @return void
415
     */
416 2
    public function quiet()
417
    {
418 2
        if (!empty($this->loggers)) {
419 2
            foreach ($this->loggers as $logger) {
420 2
                if (is_object($logger) && method_exists($logger, 'quiet')) {
421
                    try {
422 2
                        $logger->quiet();
423 2
                    } catch (\Exception $e) {
424
                        // just ignore, as we can't do anything, not even log it.
425
                    }
426
                }
427
            }
428
        }
429 2
    }
430
431
    // Deprecated uses
432
433
    /**
434
     * Keep deprecated calls from failing
435
     *
436
     * @param string $var property
437
     * @param string $val value
438
     *
439
     * @return void
440
     *
441
     * @deprecated
442
     */
443 1
    public function __set($var, $val)
444
    {
445 1
        $this->deprecatedMessage();
446
        // legacy compatibility: turn off logger display for $xoopsLogger->activated = false; usage
447 1
        if ($var==='activated' && !$val) {
448 1
            $this->quiet();
449
        }
450
451 1
    }
452
453
    /**
454
     * Keep deprecated calls from failing
455
     *
456
     * @param string $var property
457
     *
458
     * @return void
459
     *
460
     * @deprecated
461
     */
462
    public function __get($var)
463
    {
464
        $this->deprecatedMessage();
465
    }
466
467
    /**
468
     * Keep deprecated calls from failing
469
     *
470
     * @param string $method method
471
     * @param string $args   arguments
472
     *
473
     * @return void
474
     *
475
     * @deprecated
476
    */
477
    public function __call($method, $args)
478
    {
479
        $this->deprecatedMessage();
480
    }
481
482
    /**
483
     * issue a deprecated warning
484
     *
485
     * @return void
486
     */
487 1
    private function deprecatedMessage()
488
    {
489 1
        $xoops = \Xoops::getInstance();
490 1
        $xoops->deprecated('This use of XoopsLogger is deprecated since 2.6.0.');
491 1
    }
492
}
493