Passed
Push — main ( acec45...087ce0 )
by Sammy
06:55
created

LogLaddy::errorHandler()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 4
dl 0
loc 6
rs 10
1
<?php
2
3
/*
4
 * LogLaddy
5
 *
6
 * I carry a log – yes. Is it funny to you? It is not to me.
7
 * Behind all things are reasons. Reasons can even explain the absurd.
8
 *
9
 * LogLaddy manages error reporting
10
 * PSR-3 Compliant, with a NICE bonus
11
 */
12
13
namespace HexMakina\LogLaddy;
14
15
// Debugger
16
use Psr\Log\LogLevel;
17
use Psr\Log\InvalidArgumentException;
18
use HexMakina\Debugger\Debugger;
19
use HexMakina\BlackBox\StateAgentInterface;
20
21
class LogLaddy extends \Psr\Log\AbstractLogger
22
{
23
24
  /**
25
   * @var array<int,string> $level_mapping
26
   */
27
    private static array $level_mapping;
28
    private ?StateAgentInterface $state_agent;
29
30
31
    public function __construct(StateAgentInterface $agent = null)
32
    {
33
        $this->state_agent = $agent;
34
        $this->setHandlers();
35
    }
36
37
    public function setHandlers(): void
38
    {
39
        set_error_handler([$this, 'errorHandler']);
40
        set_exception_handler([$this, 'exceptionHandler']);
41
    }
42
43
    public function restoreHandlers(): void
44
    {
45
        restore_error_handler();
46
        restore_exception_handler();
47
    }
48
49
    /**
50
      * handler for errors
51
      * use set_error_handler([$instance, 'errorHandler']);
52
      *
53
      * https://www.php.net/manual/en/function.set-error-handler
54
      *
55
      */
56
    public function errorHandler(int $level, string $message, string $file = '', int $line = 0): bool
57
    {
58
        $loglevel = self::mapErrorLevelToLogLevel($level);
59
        $this->{$loglevel}($message);
60
61
        return true;
62
    }
63
64
    /*
65
    * handler for throwables,
66
    * use set_exception_handler([$instance, 'exceptionHandler']);
67
    */
68
    public function exceptionHandler(\Throwable $throwable): void
69
    {
70
        $this->critical($throwable->getMessage(), ['exception' => $throwable]);
71
    }
72
73
    public function log($level, $message, array $context = []): void
74
    {
75
        if ($level === LogLevel::DEBUG) {
76
            Debugger::visualDump($message, $level, true);
77
        } elseif (in_array($level, [LogLevel::INFO, LogLevel::NOTICE, LogLevel::WARNING])) {
78
            if (is_null($this->state_agent)) {
79
                Debugger::visualDump($message, $level, true);
80
            } else {
81
                $this->state_agent->addMessage($level, $message, $context);
82
            }
83
        } elseif (in_array($level, [LogLevel::ERROR, LogLevel::CRITICAL, LogLevel::ALERT, LogLevel::EMERGENCY])) {
84
            if (isset($context['exception']) && $context['exception'] instanceof \Throwable) {
85
                Debugger::visualDump($context['exception'], 'Uncaught ' . get_class($context['exception']), true);
86
            } else {
87
                Debugger::visualDump($message, $level, true);
88
            }
89
90
            http_response_code(500);
91
            die;
92
        } else {
93
            throw new \Psr\Log\InvalidArgumentException('UNDEFINED_LOGLEVEL_' . $level);
94
        }
95
    }
96
97
    private static function mapErrorLevelToLogLevel(int $level): string
98
    {
99
100
      // http://php.net/manual/en/errorfunc.constants.php
101
        if (empty(self::$level_mapping)) {
102
            self::createErrorLevelMap();
103
        }
104
105
        if (!isset(self::$level_mapping[$level])) {
106
            throw new \Exception(__FUNCTION__ . "($level): $level is unknown");
107
        }
108
109
        return self::$level_mapping[$level];
110
    }
111
112
   /**  Error level meaning , from \Psr\Log\LogLevel.php
113
     *  Error level mapping from \Psr\Log\LogLevel.php & http://php.net/manual/en/errorfunc.constants.php
114
     *
115
     * const EMERGENCY = 'emergency';
116
     *                 // System is unusable.
117
     * const ALERT     = 'alert';
118
     *                 // Action must be taken immediately, Example: Entire website down, database unavailable, etc.
119
     * const CRITICAL  = 'critical';
120
     *                 // Application component unavailable, unexpected exception.
121
     * const ERROR     = 'error';
122
     *                 // Run time errors that do not require immediate action
123
     * const WARNING   = 'warning';
124
     *                 // Exceptional occurrences that are not errors, undesirable things that are not necessarily wrong
125
     * const NOTICE    = 'notice';
126
     *                 // Normal but significant events.
127
     * const INFO      = 'info';
128
     *                 // Interesting events. User logs in, SQL logs.
129
     * const DEBUG     = 'debug';
130
     *                 // Detailed debug information.
131
     */
132
    private static function createErrorLevelMap(): void
133
    {
134
        $critical = array_fill_keys(
135
            [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR],
136
            LogLevel::CRITICAL
137
        );
138
139
        $error = array_fill_keys(
140
            [E_WARNING, E_CORE_WARNING, E_COMPILE_WARNING, E_USER_WARNING],
141
            LogLevel::ERROR
142
        );
143
144
        $debug = array_fill_keys(
145
            [E_NOTICE, E_USER_NOTICE, E_STRICT,E_DEPRECATED,E_USER_DEPRECATED,E_ALL],
146
            LogLevel::DEBUG
147
        );
148
149
        self::$level_mapping = $critical + $error + $debug;
150
    }
151
}
152