Completed
Pull Request — master (#605)
by Richard
18:16
created

Logger::handleError()   D

Complexity

Conditions 27
Paths 99

Size

Total Lines 76
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 80.3187

Importance

Changes 0
Metric Value
cc 27
eloc 57
nc 99
nop 4
dl 0
loc 76
ccs 32
cts 55
cp 0.5818
crap 80.3187
rs 4.1666
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
 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 22
    public static function getInstance()
68
    {
69 22
        static $instance;
70 22
        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 22
        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 (0 === error_reporting() && ($errorNumber !== E_DEPRECATED) && ($errorNumber !== E_USER_DEPRECATED)) {
97 47
            return;
98
        }
99 47
        if ($this->logging_active && $errorNumber) {
100
101
            // if an error occurs before a locale is established,
102
            // we still need messages, so check and deal with it
103
104 5
            $msg = ': ' . sprintf(
105 5
                (class_exists('\XoopsLocale', false) ? \XoopsLocale::EF_LOGGER_FILELINE : "%s in file %s line %s"),
106 5
                $this->sanitizePath($errorString),
107 5
                $this->sanitizePath($errorFile),
108 5
                $errorLine
109
            );
110
111
            switch ($errorNumber) {
112 5
                case E_USER_NOTICE:
113 1
                    $msg = (class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_ERROR : '*Error:') . $msg;
114 1
                    $this->log(LogLevel::NOTICE, $msg);
115 1
                    break;
116 4
                case E_NOTICE:
117 1
                    $msg = (class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_NOTICE : '*Notice:') . $msg;
118 1
                    $this->log(LogLevel::NOTICE, $msg);
119 1
                    break;
120 3
                case E_WARNING:
121 1
                    $msg = (class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_WARNING : '*Warning:') . $msg;
122 1
                    $this->log(LogLevel::WARNING, $msg);
123 1
                    break;
124 2
                case E_STRICT:
125 1
                    $msg = (class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_STRICT : '*Strict:') . $msg;
126 1
                    $this->log(LogLevel::WARNING, $msg);
127 1
                    break;
128 1
                case E_USER_ERROR:
129
                    $msg = (class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_ERROR : '*Error:') . $msg;
130
                    @$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

130
                    /** @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...
131
                    break;
132 1
                case E_USER_DEPRECATED:
133 1
                case E_DEPRECATED:
134
                    \Xoops::getInstance()->events()->triggerEvent('core.deprecated', array($msg));
135
                    break;
136
                default:
137 1
                    $msg = $this->getReadableErrorType($errorNumber) . $msg;
138 1
                    $this->log(LogLevel::ERROR, $msg);
139 1
                    break;
140
            }
141
        }
142
143 47
        if ($errorNumber == E_USER_ERROR) {
144
            $trace = true;
145
            if (substr($errorString, 0, '8') === 'notrace:') {
146
                $trace = false;
147
                $errorString = substr($errorString, 8);
148
            }
149
            $this->reportFatalError($errorString);
150
            if ($trace) {
151
                $trace = debug_backtrace();
152
                array_shift($trace);
153
                if ('cli' === php_sapi_name()) {
154
                    foreach ($trace as $step) {
155
                        if (isset($step['file'])) {
156
                            fprintf(STDERR, "%s (%d)\n", $this->sanitizePath($step['file']), $step['line']);
157
                        }
158
                    }
159
                } else {
160
                    echo "<div style='color:#f0f0f0;background-color:#f0f0f0'>" . _XOOPS_FATAL_BACKTRACE . ":<br />";
161
                    foreach ($trace as $step) {
162
                        if (isset($step['file'])) {
163
                            printf("%s (%d)\n<br />", $this->sanitizePath($step['file']), $step['line']);
164
                        }
165
                    }
166
                    echo '</div>';
167
                }
168
            }
169
            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...
170
        }
171 47
    }
172
173
    /**
174
     * getReadableErrorType - given a numeric $errorNumber return constant name
175
     *
176
     * @param int $errorNumber error number as used in handleError()
177
     *
178
     * @return string
179
     */
180 1
    protected function getReadableErrorType(int $errorNumber)
181
    {
182
        switch($errorNumber)
183
        {
184 1
            case E_ERROR:
185
                return 'E_ERROR';
186 1
            case E_WARNING:
187
                return 'E_WARNING';
188 1
            case E_PARSE:
189
                return 'E_PARSE';
190 1
            case E_NOTICE:
191
                return 'E_NOTICE';
192 1
            case E_CORE_ERROR:
193
                return 'E_CORE_ERROR';
194 1
            case E_CORE_WARNING:
195
                return 'E_CORE_WARNING';
196 1
            case E_COMPILE_ERROR:
197
                return 'E_COMPILE_ERROR';
198 1
            case E_COMPILE_WARNING:
199
                return 'E_COMPILE_WARNING';
200 1
            case E_USER_ERROR:
201
                return 'E_USER_ERROR';
202 1
            case E_USER_WARNING:
203
                return 'E_USER_WARNING';
204 1
            case E_USER_NOTICE:
205
                return 'E_USER_NOTICE';
206 1
            case E_STRICT:
207
                return 'E_STRICT';
208 1
            case E_RECOVERABLE_ERROR:
209
                return 'E_RECOVERABLE_ERROR';
210 1
            case E_DEPRECATED:
211
                return 'E_DEPRECATED';
212 1
            case E_USER_DEPRECATED:
213
                return 'E_USER_DEPRECATED';
214
        }
215 1
        return class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_UNKNOWN : '*Unknown:';
216
    }
217
    /**
218
     * Exception handling callback.
219
     *
220
     * @param \Exception|\Throwable $e uncaught Exception or Error
221
     *
222
     * @return void
223
     */
224
    public function handleException($e)
225
    {
226
        if ($this->isThrowable($e)) {
227
            $msg = $e->getMessage();
228
            $this->reportFatalError($msg);
229
        }
230
    }
231
232
    /**
233
     * Determine if an object implements Throwable (or is an Exception that would under PHP 7.)
234
     *
235
     * @param mixed $e Expected to be an object related to Exception or Throwable
236
     *
237
     * @return bool true if related to Throwable or Exception, otherwise false
238
     */
239
    protected function isThrowable($e)
240
    {
241
        $type = interface_exists('\Throwable', false) ? '\Throwable' : '\Exception';
242
        return $e instanceof $type;
243
    }
244
245
    /**
246
     * Announce fatal error, attempt to log
247
     *
248
     * @param string $msg error message to report
249
     *
250
     * @return void
251
     */
252
    private function reportFatalError($msg)
253
    {
254
        $msg=$this->sanitizePath($msg);
255
        if ('cli' === php_sapi_name()) {
256
            fprintf(STDERR, "\nError : %s\n", $msg);
257
        } else {
258
            if (defined('_XOOPS_FATAL_MESSAGE')) {
259
                printf(_XOOPS_FATAL_MESSAGE, XOOPS_URL, $msg);
260
            } else {
261
                printf("\nError : %s\n", $msg);
262
            }
263
        }
264
        @$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

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