Passed
Push — main ( a30411...4fdd0e )
by Sammy
03:07 queued 01:24
created

LogLaddy::reportToUser()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 11
rs 10
cc 3
nc 4
nop 3
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 HexMakina\Debugger\Debugger;
17
18
class LogLaddy implements LoggerInterface
19
{
20
    use \Psr\Log\LoggerTrait;           // PSR implementation
21
22
    public const REPORTING_USER = 'user_messages';
23
    public const INTERNAL_ERROR = 'error';
24
    public const USER_EXCEPTION = 'exception';
25
    public const LOG_LEVEL_SUCCESS = 'ok';
26
27
    private $hasHaltingMessages = false;
28
29
  /**
30
   * Everything went fine, which is always nice.
31
   * LogLaddy is a bit more optimistic than PSRLog
32
   * @param string $message
33
   * @param array  $context
34
   *
35
   * @return void
36
   */
37
    public function nice($message, array $context = array())
38
    {
39
        $this->log(LogLevel::NICE, $message, $context);
40
    }
41
42
43
    public function setHandlers()
44
    {
45
        set_error_handler([$this, 'errorHandler']);
46
        set_exception_handler([$this, 'exceptionHandler']);
47
    }
48
49
    public function restoreHandlers()
50
    {
51
        restore_error_handler();
52
        restore_exception_handler();
53
    }
54
55
    /*
56
    * handler for errors
57
    * use set_error_handler('\HexMakina\kadro\Logger\LogLaddy::error_handler')
58
    */
59
    public function errorHandler($level, $message, $file = '', $line = 0)
60
    {
61
        $loglevel = self::mapErrorLevelToLogLevel($level);
62
        $this->$loglevel($message, ['file' => $file, 'line' => $line, 'trace' => debug_backtrace()]);
63
    }
64
65
    /*
66
    * static handlers for throwables,
67
    * use set_exception_handler('\HexMakina\kadro\Logger\LogLaddy::exception_handler');
68
    */
69
    public function exceptionHandler(\Throwable $throwable)
70
    {
71
        if ($throwable instanceof \Exception) {
72
            $this->alert(self::USER_EXCEPTION, [$throwable]);
73
        } elseif ($throwable instanceof \Error) {
74
            $this->notice(self::INTERNAL_ERROR, [$throwable]);
75
        } else {
76
            $this->critical('Caught an unknown Throwable. This breaks everything.', [$throwable]);
77
        }
78
    }
79
80
    public function systemHalted($level)
81
    {
82
        switch ($level) {
83
            case LogLevel::ERROR:
84
            case LogLevel::CRITICAL:
85
            case LogLevel::ALERT:
86
            case LogLevel::EMERGENCY:
87
                return true;
88
        }
89
        return false;
90
    }
91
92
  // -- Implementation of LoggerInterface::log(), all other methods are in LoggerTrait
93
94
    public function log($level, $message, array $context = [])
95
    {
96
        $display_error = null;
97
98
      // --- Handles Throwables (exception_handler())
99
        if ($message === self::INTERNAL_ERROR || $message === self::USER_EXCEPTION) {
100
            $this->hasHaltingMessages = true;
101
            if (($context = current($context)) !== false) {
102
                $display_error = Debugger::formatThrowable($context);
103
                $display_error .= PHP_EOL . Debugger::tracesToString($context->getTrace(), false);
104
                error_log($display_error);
105
                self::HTTP500($display_error);
106
            }
107
        } elseif ($this->systemHalted($level)) { // analyses error level
108
            $display_error = sprintf(
109
                PHP_EOL . '%s in file %s:%d' . PHP_EOL . '%s',
110
                $level,
111
                Debugger::formatFilename($context['file']),
112
                $context['line'],
113
                $message
114
            );
115
116
            error_log($display_error);
117
118
            $display_error .= PHP_EOL . Debugger::tracesToString($context['trace'], true);
119
            self::HTTP500($display_error);
120
        } else {// --- Handles user messages, through SESSION storage
121
            $this->reportToUser($level, $message, $context);
122
        }
123
    }
124
125
    public static function HTTP500($display_error)
126
    {
127
        Debugger::displayErrors($display_error);
128
        http_response_code(500);
129
    }
130
  // -- Allows to know if script must be halted after fatal error
131
  // TODO NEH.. not good
132
    public function hasHaltingMessages()
133
    {
134
        return $this->hasHaltingMessages === true;
135
    }
136
137
  // -- User messages
138
139
  // -- User messages:add one
140
    public function reportToUser($level, $message, $context = [])
141
    {
142
        if (!isset($_SESSION[self::REPORTING_USER])) {
143
            $_SESSION[self::REPORTING_USER] = [];
144
        }
145
146
        if (!isset($_SESSION[self::REPORTING_USER][$level])) {
147
            $_SESSION[self::REPORTING_USER][$level] = [];
148
        }
149
150
        $_SESSION[self::REPORTING_USER][$level][] = [$message, $context];
151
    }
152
153
  // -- User messages:get all
154
    public function getUserReport()
155
    {
156
        return $_SESSION[self::REPORTING_USER] ?? [];
157
    }
158
159
  // -- User messages:reset all
160
    public function cleanUserReport()
161
    {
162
        unset($_SESSION[self::REPORTING_USER]);
163
    }
164
165
  // -- Error level mapping from \Psr\Log\LogLevel.php & http://php.net/manual/en/errorfunc.constants.php
166
  /** Error level meaning , from \Psr\Log\LogLevel.php
167
   * const EMERGENCY = 'emergency'; // System is unusable.
168
   * const ALERT     = 'alert'; // Action must be taken immediately, Example: Entire website down, database unavailable, etc.
169
   * const CRITICAL  = 'critical';  // Application component unavailable, unexpected exception.
170
   * const ERROR     = 'error'; // Run time errors that do not require immediate action
171
   * const WARNING   = 'warning'; // Exceptional occurrences that are not errors, undesirable things that are not necessarily wrong
172
   * const NOTICE    = 'notice'; // Normal but significant events.
173
   * const INFO      = 'info'; // Interesting events. User logs in, SQL logs.
174
   * const DEBUG     = 'debug'; // Detailed debug information.
175
  */
176
    private static function mapErrorLevelToLogLevel($level): string
177
    {
178
      // http://php.net/manual/en/errorfunc.constants.php
179
        $m = [];
180
181
        $m[E_ERROR] = $m[E_PARSE] = $m[E_CORE_ERROR] = $m[E_COMPILE_ERROR] = $m[E_USER_ERROR] = $m[E_RECOVERABLE_ERROR] = LogLevel::ALERT;
182
        $m[1] = $m[4] = $m[16] = $m[64] = $m[256] = $m[4096] = LogLevel::ALERT;
183
184
        $m[E_WARNING] = $m[E_CORE_WARNING] = $m[E_COMPILE_WARNING] = $m[E_USER_WARNING] = LogLevel::CRITICAL;
185
        $m[2] = $m[32] = $m[128] = $m[512] = LogLevel::CRITICAL;
186
187
        $m[E_NOTICE] = $m[E_USER_NOTICE] = LogLevel::ERROR;
188
        $m[8] = $m[1024] = LogLevel::ERROR;
189
190
        $m[E_STRICT] = $m[E_DEPRECATED] = $m[E_USER_DEPRECATED] = $m[E_ALL] = LogLevel::DEBUG;
191
        $m[2048] = $m[8192] = $m[16384] = $m[32767] = LogLevel::DEBUG;
192
193
        if (isset($m[$level])) {
194
            return $m[$level];
195
        }
196
197
        throw new \Exception(__FUNCTION__ . "($level): $level is unknown");
198
    }
199
}
200