Passed
Push — main ( 79c59e...dea438 )
by Sammy
01:23
created

LogLaddy.php (2 issues)

1
<?php
2
/*
3
 * LogLaddy
4
 *
5
 * I carry a log – yes. Is it funny to you? It is not to me.
6
 * Behind all things are reasons. Reasons can even explain the absurd.
7
 *
8
 * LogLaddy manages error reporting
9
 * PSR-3 Compliant, with a NICE bonus
10
 */
11
12
namespace HexMakina\LogLaddy;
13
14
class LogLaddy implements LoggerInterface
15
{
16
  use \Psr\Log\LoggerTrait;           // PSR implementation
17
  use \HexMakina\Debugger\Debugger;   // Debugger
18
19
  const REPORTING_USER = 'user_messages';
20
  const INTERNAL_ERROR = 'error';
21
  const USER_EXCEPTION = 'exception';
22
  const LOG_LEVEL_SUCCESS = 'ok';
23
24
  private $has_halting_messages = false;
25
26
  /**
27
   * Everything went fine, which is always nice.
28
   * LogLaddy is a bit more optimistic than PSRLog
29
   * @param string $message
30
   * @param array  $context
31
   *
32
   * @return void
33
   */
34
35
  public function nice($message, array $context = array())
36
  {
37
      $this->log(LogLevel::NICE, $message, $context);
38
  }
39
40
  // ----------------------------------------------------------- Static handlers for error, use set_error_handler('\HexMakina\kadro\Logger\LogLaddy::error_handler')
41
  public static function error_handler($level, $message, $file = '', $line = 0)
42
  {
43
    $loglevel = self::map_error_level_to_log_level($level);
44
45
    (new LogLaddy())->$loglevel($message, ['file' => $file, 'line' => $line, 'trace' => debug_backtrace()]);
46
  }
47
48
  // ----------------------------------------------------------- Static handlers for throwables, use set_exception_handler('\HexMakina\kadro\Logger\LogLaddy::exception_handler');
49
  public static function exception_handler($throwable)
50
  {
51
    $context['text'] = $throwable->getMessage();
0 ignored issues
show
Comprehensibility Best Practice introduced by
$context was never initialized. Although not strictly required by PHP, it is generally a good practice to add $context = array(); before regardless.
Loading history...
52
    $context['file'] = $throwable->getFile();
53
    $context['line'] = $throwable->getLine();
54
    $context['code'] = $throwable->getCode();
55
    $context['class'] = get_class($throwable);
56
    $context['trace'] = $throwable->getTrace();
57
58
    if(is_subclass_of($throwable, 'Error') || get_class($throwable) === 'Error')
59
      (new LogLaddy())->alert(self::INTERNAL_ERROR, $context);
60
    elseif(is_subclass_of($throwable, 'Exception') || get_class($throwable) === 'Exception')
61
      (new LogLaddy())->notice(self::USER_EXCEPTION, $context);
62
    else
63
    {
64
      die('This Throwable is not an Error or an Exception. This is unfortunate.');
65
    }
66
67
  }
68
69
  public function system_halted($level)
70
  {
71
    switch($level)
72
    {
73
      case LogLevel::ERROR:
74
      case LogLevel::CRITICAL:
75
      case LogLevel::ALERT:
76
      case LogLevel::EMERGENCY:
77
        return true;
78
    }
79
    return false;
80
  }
81
82
  // ----------------------------------------------------------- Implementation of LoggerInterface::log(), all other methods are in LoggerTrait
83
84
  public function log($level, $message, array $context = [])
85
  {
86
    $display_error = null;
87
88
    // --- Handles Throwables (exception_handler())
89
    if($message==self::INTERNAL_ERROR || $message== self::USER_EXCEPTION)
90
    {
91
      $this->has_halting_messages = true;
92
      $display_error = self::format_throwable_message($context['class'], $context['code'], $context['file'], $context['line'], $context['text']);
93
      error_log($display_error);
94
      $display_error.= self::format_trace($context['trace'], false);
95
      self::HTTP_500($display_error);
96
    }
97
    elseif($this->system_halted($level)) // analyses error level
98
    {
99
      $display_error = sprintf(PHP_EOL.'%s in file %s:%d'.PHP_EOL.'%s', $level, self::format_file($context['file']), $context['line'], $message);
100
      error_log($display_error);
101
      $display_error.= self::format_trace($context['trace'], true);
102
      self::HTTP_500($display_error);
103
    }
104
    else
105
    {// --- Handles user messages, through SESSION storage
106
      $this->report_to_user($level, $message, $context);
107
    }
108
109
    // --- may of may not show errors, depends on environment
110
111
  }
112
113
  public static function HTTP_500($display_error)
114
  {
115
    self::display_errors($display_error);
116
    http_response_code(500);
117
    die;
0 ignored issues
show
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...
118
  }
119
  // ----------------------------------------------------------- Allows to know if script must be halted after fatal error
120
  // TODO NEH.. not good
121
  public function has_halting_messages()
122
  {
123
    return $this->has_halting_messages === true;
124
  }
125
126
  // ----------------------------------------------------------- User messages
127
128
  // ----------------------------------------------------------- User messages:add one
129
  public function report_to_user($level, $message, $context = [])
130
  {
131
    if(!isset($_SESSION[self::REPORTING_USER]))
132
      $_SESSION[self::REPORTING_USER] = [];
133
134
    if(!isset($_SESSION[self::REPORTING_USER][$level]))
135
      $_SESSION[self::REPORTING_USER][$level] = [];
136
137
    $_SESSION[self::REPORTING_USER][$level][] = [$message, $context];
138
  }
139
140
  // ----------------------------------------------------------- User messages:get all
141
  public function get_user_report()
142
  {
143
    return $_SESSION[self::REPORTING_USER] ?? [];
144
  }
145
146
  // ----------------------------------------------------------- User messages:reset all
147
  public function clean_user_report()
148
  {
149
    unset($_SESSION[self::REPORTING_USER]);
150
  }
151
152
  // ----------------------------------------------------------- Error level mapping from \Psr\Log\LogLevel.php & http://php.net/manual/en/errorfunc.constants.php
153
  /** Error level meaning , from \Psr\Log\LogLevel.php
154
   * const EMERGENCY = 'emergency'; // System is unusable.
155
   * const ALERT     = 'alert'; // Action must be taken immediately, Example: Entire website down, database unavailable, etc.
156
   * const CRITICAL  = 'critical';  // Application component unavailable, unexpected exception.
157
   * const ERROR     = 'error'; // Run time errors that do not require immediate action
158
   * const WARNING   = 'warning'; // Exceptional occurrences that are not errors, undesirable things that are not necessarily wrong
159
   * const NOTICE    = 'notice'; // Normal but significant events.
160
   * const INFO      = 'info'; // Interesting events. User logs in, SQL logs.
161
   * const DEBUG     = 'debug'; // Detailed debug information.
162
  */
163
  private static function map_error_level_to_log_level($level) : string
164
  {
165
    // http://php.net/manual/en/errorfunc.constants.php
166
    $m=[];
167
168
    $m[E_ERROR]=$m[E_PARSE]=$m[E_CORE_ERROR]=$m[E_COMPILE_ERROR]=$m[E_USER_ERROR]=$m[E_RECOVERABLE_ERROR]=LogLevel::ALERT;
169
    $m[1]=$m[4]=$m[16]=$m[64]=$m[256]=$m[4096]=LogLevel::ALERT;
170
171
    $m[E_WARNING]=$m[E_CORE_WARNING]=$m[E_COMPILE_WARNING]=$m[E_USER_WARNING]=LogLevel::CRITICAL;
172
    $m[2]=$m[32]=$m[128]=$m[512]=LogLevel::CRITICAL;
173
174
    $m[E_NOTICE]=$m[E_USER_NOTICE]=LogLevel::ERROR;
175
    $m[8]=$m[1024]=LogLevel::ERROR;
176
177
    $m[E_STRICT]=$m[E_DEPRECATED]=$m[E_USER_DEPRECATED]=$m[E_ALL]=LogLevel::DEBUG;
178
    $m[2048]=$m[8192]=$m[16384]=$m[32767]=LogLevel::DEBUG;
179
180
    if(isset($m[$level]))
181
      return $m[$level];
182
183
    throw new \Exception(__FUNCTION__."($level): $level is unknown");
184
  }
185
}
186