Completed
Push — datatype ( 11f376 )
by Richard
11:36
created

Logger::reportFatalError()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 1
dl 0
loc 13
ccs 0
cts 8
cp 0
crap 12
rs 9.9666
c 0
b 0
f 0
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 92
    public function handleError($errorNumber, $errorString, $errorFile, $errorLine)
95
    {
96 92
        if (0 === error_reporting() && ($errorNumber !== E_DEPRECATED) && ($errorNumber !== E_USER_DEPRECATED)) {
97 47
            return;
98
        }
99 48
        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 48
        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 48
    }
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 1
        static $lookupTable = [
183
            E_ERROR => 'E_ERROR',
184
            E_WARNING => 'E_WARNING',
185
            E_PARSE => 'E_PARSE',
186
            E_NOTICE => 'E_NOTICE',
187
            E_CORE_ERROR => 'E_CORE_ERROR',
188
            E_CORE_WARNING => 'E_CORE_WARNING',
189
            E_COMPILE_ERROR => 'E_COMPILE_ERROR',
190
            E_COMPILE_WARNING => 'E_COMPILE_WARNING',
191
            E_USER_ERROR => 'E_USER_ERROR',
192
            E_USER_WARNING => 'E_USER_WARNING',
193
            E_USER_NOTICE => 'E_USER_NOTICE',
194
            E_STRICT => 'E_STRICT',
195
            E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
196
            E_DEPRECATED => 'E_DEPRECATED',
197
            E_USER_DEPRECATED => 'E_USER_DEPRECATED',
198
        ];
199
200 1
        if (null !== $lookupTable[$errorNumber]) {
201
            return $lookupTable[$errorNumber];
202
        }
203 1
        return class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_UNKNOWN : '*Unknown:';
204
}
205
    /**
206
     * Exception handling callback.
207
     *
208
     * @param \Exception|\Throwable $e uncaught Exception or Error
209
     *
210
     * @return void
211
     */
212
    public function handleException($e)
213
    {
214
        if ($this->isThrowable($e)) {
215
            $msg = $e->getMessage();
216
            $this->reportFatalError($msg);
217
        }
218
    }
219
220
    /**
221
     * Determine if an object implements Throwable (or is an Exception that would under PHP 7.)
222
     *
223
     * @param mixed $e Expected to be an object related to Exception or Throwable
224
     *
225
     * @return bool true if related to Throwable or Exception, otherwise false
226
     */
227
    protected function isThrowable($e)
228
    {
229
        $type = interface_exists('\Throwable', false) ? '\Throwable' : '\Exception';
230
        return $e instanceof $type;
231
    }
232
233
    /**
234
     * Announce fatal error, attempt to log
235
     *
236
     * @param string $msg error message to report
237
     *
238
     * @return void
239
     */
240
    private function reportFatalError($msg)
241
    {
242
        $msg=$this->sanitizePath($msg);
243
        if ('cli' === php_sapi_name()) {
244
            fprintf(STDERR, "\nError : %s\n", $msg);
245
        } else {
246
            if (defined('_XOOPS_FATAL_MESSAGE')) {
247
                printf(_XOOPS_FATAL_MESSAGE, XOOPS_URL, $msg);
248
            } else {
249
                printf("\nError : %s\n", $msg);
250
            }
251
        }
252
        @$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

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