Passed
Push — main ( 5ac80e...93508d )
by Sammy
01:29
created

LogLaddy::has_halting_messages()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
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 and crud tracking
9
 * PSR-3 Compliant, where NOTICE is to be read as SUCCESS
10
 */
11
12
namespace HexMakina\kadro\Logger;
13
14
use \HexMakina\Crudites\{Crudites,CruditesException,Table};
0 ignored issues
show
Bug introduced by
The type HexMakina\Crudites\CruditesException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
The type HexMakina\Crudites\Crudites was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Bug introduced by
The type HexMakina\Crudites\Table was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
15
use \HexMakina\Crudites\TableInterface;
0 ignored issues
show
Bug introduced by
The type HexMakina\Crudites\TableInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
17
18
class LogLaddy implements LoggerInterface
19
{
20
  use \Psr\Log\LoggerTrait; // PSR implementation
21
  use \HexMakina\Debugger\Debugger;
22
23
  const LOG_TABLE_NAME = 'kadro_action_logger';
24
  const REPORTING_USER = 'user_messages';
25
26
  const INTERNAL_ERROR = 'error';
27
28
  // TODO: separate KadroException (parent of all operational exceptions)
29
  // from \Exception (parent of all exception)
30
  // CruditesException must Extend KadroException, and all throw new \Exception must be S & D
31
  // Only then the notion of USER_EXCEPTION will be pertinent
32
  // create KadroException, ew constant KADRO_EXCEPTION, adapt code, test ..
33
  const USER_EXCEPTION = 'exception';
34
35
  const LOG_LEVEL_SUCCESS = 'ok';
36
37
  private $has_halting_messages = false;
38
39
40
  /**
41
   * Everything went fine, which is always nice.
42
   * LogLaddy, being born yesterday, is a bit more optimistic than PSRLog
43
   * @param string $message
44
   * @param array  $context
45
   *
46
   * @return void
47
   */
48
  public function nice($message, array $context = array())
49
  {
50
      $this->log(LogLevel::NICE, $message, $context);
51
  }
52
53
  // ----------------------------------------------------------- Static handlers for error, use set_error_handler('\HexMakina\kadro\Logger\LogLaddy::error_handler')
54
  public static function error_handler($level, $message, $file = '', $line = 0)
55
  {
56
    $loglevel = self::map_error_level_to_log_level($level);
57
58
    (new LogLaddy())->$loglevel($message, ['file' => $file, 'line' => $line, 'trace' => debug_backtrace()]);
59
  }
60
61
  // ----------------------------------------------------------- Static handlers for throwables, use set_exception_handler('\HexMakina\kadro\Logger\LogLaddy::exception_handler');
62
  public static function exception_handler($throwable)
63
  {
64
    $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...
65
    $context['file'] = $throwable->getFile();
66
    $context['line'] = $throwable->getLine();
67
    $context['code'] = $throwable->getCode();
68
    $context['class'] = get_class($throwable);
69
    $context['trace'] = $throwable->getTrace();
70
71
    if(is_subclass_of($throwable, 'Error') || get_class($throwable) === 'Error')
72
      (new LogLaddy())->alert(self::INTERNAL_ERROR, $context);
73
    elseif(is_subclass_of($throwable, 'Exception') || get_class($throwable) === 'Exception')
74
      (new LogLaddy())->notice(self::USER_EXCEPTION, $context);
75
    else
76
    {
77
      die('This Throwable is not an Error or an Exception. This is unfortunate.');
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...
78
    }
79
80
  }
81
82
  public function system_halted($level)
83
  {
84
85
    switch($level)
86
    {
87
      case LogLevel::ERROR:
88
      case LogLevel::CRITICAL:
89
      case LogLevel::ALERT:
90
      case LogLevel::EMERGENCY:
91
        return true;
92
    }
93
    return false;
94
  }
95
96
  // ----------------------------------------------------------- Implementation of LoggerInterface::log(), all other methods are in LoggerTrait
97
98
  public function log($level, $message, array $context = [])
99
  {
100
101
    $display_error = null;
102
103
    // --- Handles Throwables (exception_handler())
104
    if($message==self::INTERNAL_ERROR || $message== self::USER_EXCEPTION)
105
    {
106
      $this->has_halting_messages = true;
107
      $display_error = \HexMakina\Debugger\Debugger::format_throwable_message($context['class'], $context['code'], $context['file'], $context['line'], $context['text']);
108
      error_log($display_error);
109
      $display_error.= \HexMakina\Debugger\Debugger::format_trace($context['trace'], false);
110
      self::HTTP_500($display_error);
111
    }
112
    elseif($this->system_halted($level)) // analyses error level
113
    {
114
      $display_error = sprintf(PHP_EOL.'%s in file %s:%d'.PHP_EOL.'%s', $level, \HexMakina\Debugger\Debugger::format_file($context['file']), $context['line'], $message);
115
      error_log($display_error);
116
      $display_error.= \HexMakina\Debugger\Debugger::format_trace($context['trace'], false);
117
      self::HTTP_500($display_error);
118
    }
119
    else
120
    {// --- Handles user messages, through SESSION storage
121
      $this->report_to_user($level, $message, $context);
122
    }
123
124
    // --- may of may not show errors, depends on environment
125
126
  }
127
128
  public static function HTTP_500($display_error)
129
  {
130
    \HexMakina\Debugger\Debugger::display_errors($display_error);
131
    http_response_code(500);
132
    die;
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...
133
  }
134
  // ----------------------------------------------------------- Allows to know if script must be halted after fatal error
135
  // TODO NEH.. not good
136
  public function has_halting_messages()
137
  {
138
    return $this->has_halting_messages === true;
139
  }
140
141
  // ----------------------------------------------------------- User messages
142
143
  // ----------------------------------------------------------- User messages:add one
144
  /* Before decoupling with Lezer
145
  public function report_to_user($level, $message, $context = [])
146
  {
147
    {
148
    if(defined("L::$message")) // message isa translatable code
149
      foreach($context as $i => $param) // message need translated params
150
        if(defined("L::$param"))
151
          $context[$i] = L($param);
152
153
      $message = L($message, $context);
154
    }
155
156
    // $_SESSION[self::REPORTING_USER][$level][] = date('Y-m-d H:i:s.u').' '.$message;
157
    // ddt($message);
158
    $_SESSION[self::REPORTING_USER] = $_SESSION[self::REPORTING_USER] ?? [];
159
    $_SESSION[self::REPORTING_USER][$level] = $_SESSION[self::REPORTING_USER][$level] ?? [];
160
161
    // $_SESSION[self::REPORTING_USER][$level][] = $message; // this
162
    $_SESSION[self::REPORTING_USER][$level][] = [$message, $context];
163
  }
164
*/
165
  // ----------------------------------------------------------- User messages:add one
166
  public function report_to_user($level, $message, $context = [])
167
  {
168
    if(!isset($_SESSION[self::REPORTING_USER]))
169
      $_SESSION[self::REPORTING_USER] = [];
170
171
    if(!isset($_SESSION[self::REPORTING_USER][$level]))
172
      $_SESSION[self::REPORTING_USER][$level] = [];
173
174
    $_SESSION[self::REPORTING_USER][$level][] = [$message, $context];
175
  }
176
177
  // ----------------------------------------------------------- User messages:get all
178
  public function get_user_report()
179
  {
180
    return $_SESSION[self::REPORTING_USER] ?? [];
181
  }
182
183
  // ----------------------------------------------------------- User messages:reset all
184
  public function clean_user_report()
185
  {
186
    unset($_SESSION[self::REPORTING_USER]);
187
  }
188
189
  // ----------------------------------------------------------- CRUD Tracking:get for one model
190
  // public function history($table, $id, $sort='DESC')
191
  // {
192
  //   $table_alias = 'logladdy';
193
  //   $table = Crudites::inspect(self::LOG_TABLE_NAME);
194
  //   $q = $table->select(["$table_alias.*", 'name'], $table_alias);
195
  //   $q->join([User::table_name(), 'u'], [[$table_alias,'query_by', 'u','id']], 'INNER');
196
  //   $q->aw_fields_eq(['query_table' => $table, 'query_id' => $id], $table_alias);
197
  //
198
  //   $q->order_by(['query_on', $sort]);
199
  //   $q->run();
200
  //   $res = $q->ret_ass();
201
  //
202
  //   return $res;
203
  // }
204
205
  // ----------------------------------------------------------- CRUD Tracking:get for many models
206
  public function changes($options=[])
207
  {
208
209
    if(!isset($options['limit']) || empty($options['limit']))
210
      $limit = 1000;
211
    else  $limit = intval($options['limit']);
212
213
    // TODO SELECT field order can't change without adapting the result parsing code (foreach $res)
214
    $table = Crudites::inspect(self::LOG_TABLE_NAME);
215
    $select_fields = ['SUBSTR(query_on, 1, 10) AS working_day', 'query_table', 'query_id',  'GROUP_CONCAT(DISTINCT query_type, "-", query_by) as action_by'];
216
    $q = $table->select($select_fields);
217
    $q->order_by(['', 'working_day', 'DESC']);
218
    $q->order_by([self::LOG_TABLE_NAME, 'query_table', 'DESC']);
219
    $q->order_by([self::LOG_TABLE_NAME, 'query_id', 'DESC']);
220
221
    $q->group_by('working_day');
222
    $q->group_by('query_table');
223
    $q->group_by('query_id');
224
    $q->having("action_by NOT LIKE '%D%'");
225
    $q->limit($limit);
226
227
    foreach($options as $o => $v)
228
    {
229
          if(preg_match('/id/', $o))                    $q->aw_eq('query_id', $v);
230
      elseif(preg_match('/tables/', $o))                $q->aw_string_in('query_table', is_array($v) ? $v : [$v]);
231
      elseif(preg_match('/table/', $o))                 $q->aw_eq('query_table', $v);
232
      elseif(preg_match('/(type|action)/', $o))         $q->aw_string_in('query_type', is_array($v) ? $v : [$v]);
233
      elseif(preg_match('/(date|query_on)/', $o))       $q->aw_like('query_on', "$v%");
234
      elseif(preg_match('/(oper|user|query_by)/', $o))  $q->aw_eq('query_by', $v);
235
    }
236
237
    try{$q->run();}
238
    catch(CruditesException $e){vdt($e);return false;}
0 ignored issues
show
Bug introduced by
The function vdt was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

238
    catch(CruditesException $e){/** @scrutinizer ignore-call */ vdt($e);return false;}
Loading history...
239
240
    $res = $q->ret_num(); // ret num to list()
241
    // ddt($res);
242
    $ret = [];
243
244
    foreach($res as $r)
245
    {
246
      list($working_day, $class, $instance_id, $logs) = $r;
247
248
      if(!isset($ret[$working_day]))
249
        $ret[$working_day] = [];
250
      if(!isset($ret[$working_day][$class]))
251
        $ret[$working_day][$class] = [];
252
253
      $ret[$working_day][$class][$instance_id] = $logs;
254
    }
255
    return $ret;
256
  }
257
258
  // ----------------------------------------------------------- Error level mapping from \Psr\Log\LogLevel.php & http://php.net/manual/en/errorfunc.constants.php
259
  /** Error level meaning , from \Psr\Log\LogLevel.php
260
   * const EMERGENCY = 'emergency'; // System is unusable.
261
   * const ALERT     = 'alert'; // Action must be taken immediately, Example: Entire website down, database unavailable, etc.
262
   * const CRITICAL  = 'critical';  // Application component unavailable, unexpected exception.
263
   * const ERROR     = 'error'; // Run time errors that do not require immediate action
264
   * const WARNING   = 'warning'; // Exceptional occurrences that are not errors, undesirable things that are not necessarily wrong
265
   * const NOTICE    = 'notice'; // Normal but significant events.
266
   * const INFO      = 'info'; // Interesting events. User logs in, SQL logs.
267
   * const DEBUG     = 'debug'; // Detailed debug information.
268
  */
269
  private static function map_error_level_to_log_level($level) : string
270
  {
271
    // http://php.net/manual/en/errorfunc.constants.php
272
273
    $m[E_ERROR]=$m[E_PARSE]=$m[E_CORE_ERROR]=$m[E_COMPILE_ERROR]=$m[E_USER_ERROR]=$m[E_RECOVERABLE_ERROR]=LogLevel::ALERT;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$m was never initialized. Although not strictly required by PHP, it is generally a good practice to add $m = array(); before regardless.
Loading history...
274
    $m[1]=$m[4]=$m[16]=$m[64]=$m[256]=$m[4096]=LogLevel::ALERT;
275
276
    $m[E_WARNING]=$m[E_CORE_WARNING]=$m[E_COMPILE_WARNING]=$m[E_USER_WARNING]=LogLevel::CRITICAL;
277
    $m[2]=$m[32]=$m[128]=$m[512]=LogLevel::CRITICAL;
278
279
    $m[E_NOTICE]=$m[E_USER_NOTICE]=LogLevel::ERROR;
280
    $m[8]=$m[1024]=LogLevel::ERROR;
281
282
    $m[E_STRICT]=$m[E_DEPRECATED]=$m[E_USER_DEPRECATED]=$m[E_ALL]=LogLevel::DEBUG;
283
    $m[2048]=$m[8192]=$m[16384]=$m[32767]=LogLevel::DEBUG;
284
285
    if(isset($m[$level]))
286
      return $m[$level];
287
288
    throw new \Exception(__FUNCTION__."($level): $level is unknown");
289
  }
290
}
291