1 | <?php |
||||||
2 | |||||||
3 | /** |
||||||
4 | * TLogger class file |
||||||
5 | * |
||||||
6 | * @author Qiang Xue <[email protected]> |
||||||
7 | * @link https://github.com/pradosoft/prado |
||||||
8 | * @license https://github.com/pradosoft/prado/blob/master/LICENSE |
||||||
9 | */ |
||||||
10 | |||||||
11 | namespace Prado\Util; |
||||||
12 | |||||||
13 | use Prado\Prado; |
||||||
14 | use Prado\TEventParameter; |
||||||
15 | use Prado\Web\UI\TControl; |
||||||
16 | |||||||
17 | /** |
||||||
18 | * TLogger class. |
||||||
19 | * |
||||||
20 | * TLogger records log messages in memory and implements the methods to |
||||||
21 | * retrieve the messages with filter conditions, including log levels, |
||||||
22 | * log categories, and by control. |
||||||
23 | * |
||||||
24 | * Filtering categories and controls can use a '!' or '~' prefix and the element |
||||||
25 | * will be excluded rather than included. Using a trailing '*' will include |
||||||
26 | * elements starting with the specified text. |
||||||
27 | * |
||||||
28 | * The log message, log level, log category (class), microtime Time stamp |
||||||
29 | * memory used, control ID, traces, and process ID. |
||||||
30 | * |
||||||
31 | * @author Qiang Xue <[email protected]> |
||||||
32 | * @author Brad Anderson <[email protected]> |
||||||
33 | * @since 3.0 |
||||||
34 | */ |
||||||
35 | class TLogger extends \Prado\TComponent |
||||||
36 | { |
||||||
37 | /** |
||||||
38 | * Log levels. |
||||||
39 | */ |
||||||
40 | public const DEBUG = 0x01; |
||||||
41 | public const INFO = 0x02; |
||||||
42 | public const NOTICE = 0x04; |
||||||
43 | public const WARNING = 0x08; |
||||||
44 | public const ERROR = 0x10; |
||||||
45 | public const ALERT = 0x20; |
||||||
46 | public const FATAL = 0x40; |
||||||
47 | |||||||
48 | public const PROFILE = 0x100; |
||||||
49 | public const PROFILE_BEGIN = 0x300; |
||||||
50 | public const PROFILE_END = 0x500; |
||||||
51 | |||||||
52 | /** |
||||||
53 | * These are for selecting just the PROFILE_BEGIN and PROFILE_END logs without |
||||||
54 | * the PROFILE bit. |
||||||
55 | */ |
||||||
56 | public const PROFILE_BEGIN_SELECT = 0x200; |
||||||
57 | public const PROFILE_END_SELECT = 0x400; |
||||||
58 | |||||||
59 | public const LOGGED = 0x8000; |
||||||
60 | |||||||
61 | /** |
||||||
62 | * Log Information Index. |
||||||
63 | */ |
||||||
64 | public const LOG_MESSAGE = 0; |
||||||
65 | public const LOG_LEVEL = 1; |
||||||
66 | public const LOG_CATEGORY = 2; |
||||||
67 | public const LOG_TIME = 3; |
||||||
68 | public const LOG_MEMORY = 4; |
||||||
69 | 32 | public const LOG_CONTROL = 5; |
|||||
70 | public const LOG_TRACES = 6; |
||||||
71 | 32 | public const LOG_PID = 7; |
|||||
72 | |||||||
73 | /** |
||||||
74 | * @var array log messages |
||||||
75 | */ |
||||||
76 | private array $_logs = []; |
||||||
77 | /** |
||||||
78 | 32 | * @var array unmatched PROFILE_BEGIN log messages |
|||||
79 | * @since 4.3.0 |
||||||
80 | 32 | */ |
|||||
81 | 32 | private array $_profileLogs = []; |
|||||
82 | /** |
||||||
83 | * @var int the profileLogs count that are not static::LOGGED. |
||||||
84 | * @since 4.3.0 |
||||||
85 | */ |
||||||
86 | private int $_profileLogsCount = 0; |
||||||
87 | /** |
||||||
88 | * @var array The maintained Profile Begin times |
||||||
89 | * @since 4.3.0 |
||||||
90 | */ |
||||||
91 | private array $_profileBeginTimes = []; |
||||||
92 | /** |
||||||
93 | * @var ?int log levels (bits) to be filtered |
||||||
94 | */ |
||||||
95 | private ?int $_levels = null; |
||||||
96 | /** |
||||||
97 | * @var ?array list of categories to be filtered |
||||||
98 | */ |
||||||
99 | private ?array $_categories = null; |
||||||
100 | /** |
||||||
101 | * @var array list of control client ids to be filtered |
||||||
102 | */ |
||||||
103 | private ?array $_controls = null; |
||||||
104 | /** |
||||||
105 | * @var ?float timestamp used to filter |
||||||
106 | */ |
||||||
107 | private ?float $_timestamp; |
||||||
108 | /** |
||||||
109 | * @var ?int process id used to filter |
||||||
110 | * @since 4.3.0 |
||||||
111 | */ |
||||||
112 | private ?int $_pid; |
||||||
113 | |||||||
114 | /** |
||||||
115 | * @var int the number of logs before flushing. |
||||||
116 | * @since 4.3.0 |
||||||
117 | */ |
||||||
118 | private int $_flushCount = 1000; |
||||||
119 | /** |
||||||
120 | * @var bool is the logger flushing. |
||||||
121 | * @since 4.3.0 |
||||||
122 | */ |
||||||
123 | private bool $_flushing = false; |
||||||
124 | /** |
||||||
125 | * @var ?array any logged messages during flushing so they aren't flushed. |
||||||
126 | * @since 4.3.0 |
||||||
127 | */ |
||||||
128 | private ?array $_flushingLog = null; |
||||||
129 | /** |
||||||
130 | * @var int The depth of a trace, default 0 for no trace. |
||||||
131 | * @since 4.3.0 |
||||||
132 | */ |
||||||
133 | private int $_traceLevel = 0; |
||||||
134 | /** |
||||||
135 | * @var bool Is the logger Registered with onEndRequest |
||||||
136 | * @since 4.3.0 |
||||||
137 | */ |
||||||
138 | private bool $_registered = false; |
||||||
139 | |||||||
140 | /** |
||||||
141 | * @param bool $flushShutdown Should onFlushLogs be a register_shutdown_function. |
||||||
142 | * @since 4.3.0 |
||||||
143 | */ |
||||||
144 | public function __construct(bool $flushShutdown = true) |
||||||
145 | { |
||||||
146 | if ($flushShutdown) { |
||||||
147 | register_shutdown_function(function () { |
||||||
148 | $this->onFlushLogs(); |
||||||
149 | // flush any logs in the shutdown function. |
||||||
150 | register_shutdown_function([$this, 'onFlushLogs'], $this, true); |
||||||
151 | }); |
||||||
152 | } |
||||||
153 | parent::__construct(); |
||||||
154 | } |
||||||
155 | |||||||
156 | /** |
||||||
157 | * @return int The number of logs before triggering {@see self::onFlushLogs()} |
||||||
158 | * @since 4.3.0 |
||||||
159 | */ |
||||||
160 | public function getFlushCount(): int |
||||||
161 | { |
||||||
162 | return $this->_flushCount; |
||||||
163 | } |
||||||
164 | |||||||
165 | /** |
||||||
166 | * @param int $value the number of logs before triggering {@see self::onFlushLogs()} |
||||||
167 | * @return static $this |
||||||
168 | * @since 4.3.0 |
||||||
169 | */ |
||||||
170 | public function setFlushCount(int $value): static |
||||||
171 | { |
||||||
172 | $this->_flushCount = $value; |
||||||
173 | |||||||
174 | $this->checkProfileLogsSize(); |
||||||
175 | $this->checkLogsSize(); |
||||||
176 | |||||||
177 | return $this; |
||||||
178 | } |
||||||
179 | |||||||
180 | /** |
||||||
181 | * @return int How much debug trace stack information to include. |
||||||
182 | * @since 4.3.0 |
||||||
183 | */ |
||||||
184 | public function getTraceLevel(): int |
||||||
185 | { |
||||||
186 | return $this->_traceLevel; |
||||||
187 | } |
||||||
188 | |||||||
189 | /** |
||||||
190 | * @param int $value How much debug trace stack information to include. |
||||||
191 | * @return static $this |
||||||
192 | * @since 4.3.0 |
||||||
193 | */ |
||||||
194 | public function setTraceLevel(int $value): static |
||||||
195 | { |
||||||
196 | $this->_traceLevel = $value; |
||||||
197 | |||||||
198 | return $this; |
||||||
199 | } |
||||||
200 | |||||||
201 | /** |
||||||
202 | * This returns the number of logs being held. When the $selector the following |
||||||
203 | * is returned: |
||||||
204 | * - null is the ~ number of logs a route will receive |
||||||
205 | * - 0 is the ~ number of logs to be collected before removing static::LOGGGED logs |
||||||
206 | * - true is the number of logs without profile begin |
||||||
207 | * - false is the number of profile begin logs |
||||||
208 | * |
||||||
209 | * When a PROFILE_BEGIN is flushed without a paired PROFILE_END, then it is logged |
||||||
210 | * and flagged as static::LOGGED. These log items will be resent until and with |
||||||
211 | * a paired PROFILE_END. The PROFILE_BEGIN are relogged to computing timing but are |
||||||
212 | * removed before being processed by the log route. |
||||||
213 | * |
||||||
214 | * @param ?bool $selector Which list of logs to count. Default null for both. |
||||||
215 | * @return int The number of logs. |
||||||
216 | * @since 4.3.0 |
||||||
217 | */ |
||||||
218 | public function getLogCount(null|bool|int $selector = null): int |
||||||
219 | { |
||||||
220 | if ($selector === null) {// logs @TLogRoute::processLogs after removing logged elements. |
||||||
221 | return count($this->_logs) + $this->_profileLogsCount; |
||||||
222 | } elseif ($selector === 0) {// logs @TLogRoute::collectLogs, from TLogger::getLogs |
||||||
223 | return count($this->_logs) + count($this->_profileLogs); |
||||||
224 | } elseif ($selector) { |
||||||
225 | return count($this->_logs); |
||||||
226 | } //elseif false |
||||||
227 | return count($this->_profileLogs); |
||||||
228 | } |
||||||
229 | |||||||
230 | /** |
||||||
231 | * This is the number of Profile Begin Logs that have been logged. |
||||||
232 | * @return int The Profile Logs already logged. |
||||||
233 | * @since 4.3.0 |
||||||
234 | */ |
||||||
235 | public function getLoggedProfileLogCount(): int |
||||||
236 | { |
||||||
237 | return count($this->_profileLogs) - $this->_profileLogsCount; |
||||||
238 | } |
||||||
239 | |||||||
240 | /** |
||||||
241 | * Ensures that the logger is registered with the Application onEndRequest. |
||||||
242 | * @since 4.3.0 |
||||||
243 | */ |
||||||
244 | protected function ensureFlushing() |
||||||
245 | { |
||||||
246 | if ($this->_registered) { |
||||||
247 | return; |
||||||
248 | } |
||||||
249 | |||||||
250 | if ($app = Prado::getApplication()) { |
||||||
251 | $app->attachEventHandler('onEndRequest', [$this, 'onFlushLogs'], 20); |
||||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||||
252 | $this->_registered = true; |
||||||
253 | } |
||||||
254 | } |
||||||
255 | |||||||
256 | /** |
||||||
257 | * Adds a single log item |
||||||
258 | * @param array $log The log item to log. |
||||||
259 | * @param bool $flush Allow a flush on adding if the log size is FlushCount, default true. |
||||||
260 | * @return ?float The time delta for PROFILE_END; and null in other cases. |
||||||
261 | * @since 4.3.0 |
||||||
262 | */ |
||||||
263 | protected function addLog(array $log, bool $flush = true): ?float |
||||||
264 | { |
||||||
265 | $return = null; |
||||||
266 | if ($this->_flushing) { |
||||||
267 | $this->_flushingLog[] = $log; |
||||||
268 | return $return; |
||||||
269 | } |
||||||
270 | if ($log[static::LOG_LEVEL] & TLogger::PROFILE_BEGIN_SELECT) { |
||||||
271 | $profileName = $this->dyProfilerRename('[' . $log[static::LOG_MESSAGE] . '][' . $log[static::LOG_PID] . ']'); |
||||||
0 ignored issues
–
show
The method
dyProfilerRename() does not exist on Prado\Util\TLogger . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
272 | $add = !isset($this->_profileLogs[$profileName]); |
||||||
273 | $this->_profileLogs[$profileName] = $log; |
||||||
274 | $this->_profileBeginTimes[$profileName] = $log[static::LOG_TIME]; |
||||||
275 | if ($add) { |
||||||
276 | $this->_profileLogsCount++; |
||||||
277 | $this->checkProfileLogsSize($flush); |
||||||
278 | } |
||||||
279 | } else { |
||||||
280 | if ($log[static::LOG_LEVEL] & TLogger::PROFILE_END_SELECT) { // Move ProfileBegin to the primary log. |
||||||
281 | $profileName = $this->dyProfilerRename('[' . $log[static::LOG_MESSAGE] . '][' . $log[static::LOG_PID] . ']'); |
||||||
282 | if (isset($this->_profileLogs[$profileName])) { |
||||||
283 | $this->_logs[] = $this->_profileLogs[$profileName]; |
||||||
284 | |||||||
285 | if (!($this->_profileLogs[$profileName][static::LOG_LEVEL] & static::LOGGED)) { |
||||||
286 | $this->_profileLogsCount--; |
||||||
287 | } |
||||||
288 | |||||||
289 | unset($this->_profileLogs[$profileName]); |
||||||
290 | } |
||||||
291 | if (isset($this->_profileBeginTimes[$profileName])) { |
||||||
292 | $return = $log[static::LOG_TIME] - $this->_profileBeginTimes[$profileName]; |
||||||
293 | } |
||||||
294 | } |
||||||
295 | $this->_logs[] = $log; |
||||||
296 | $this->checkLogsSize($flush); |
||||||
297 | } |
||||||
298 | return $return; |
||||||
299 | } |
||||||
300 | |||||||
301 | /** |
||||||
302 | * When the ProfileLog Count is equal or more than the FlushCount, then the Retained |
||||||
303 | * profile logs are deleted and a WARNING is logged. |
||||||
304 | * @param bool $flush Is the log being flushing, default true. |
||||||
305 | * @since 4.3.0 |
||||||
306 | */ |
||||||
307 | protected function checkProfileLogsSize(bool $flush = true) |
||||||
308 | { |
||||||
309 | if ($this->_flushCount && $this->getLogCount(false) >= $this->_flushCount) { |
||||||
310 | $this->deleteProfileLogs(); |
||||||
311 | $saved = null; |
||||||
312 | if (!$flush) { |
||||||
313 | $saved = $this->_flushCount; |
||||||
314 | $this->_flushCount = 0; |
||||||
315 | } |
||||||
316 | $this->log("Too many retained profiler logs", static::WARNING, self::class . '\Profiler'); |
||||||
317 | if (!$flush) { |
||||||
318 | $this->_flushCount = $saved; |
||||||
319 | } |
||||||
320 | } |
||||||
321 | } |
||||||
322 | |||||||
323 | /** |
||||||
324 | * If we allow a flush (not a bulk logging), and the log count is equal to or more |
||||||
325 | * than the FlushCount then we flush the logs. |
||||||
326 | * @param bool $flush Is the log being flushing, default true. |
||||||
327 | * @since 4.3.0 |
||||||
328 | */ |
||||||
329 | protected function checkLogsSize(bool $flush = true) |
||||||
330 | { |
||||||
331 | if ($flush && $this->_flushCount && $this->getLogCount() >= $this->_flushCount) { |
||||||
332 | $this->onFlushLogs(); |
||||||
333 | } |
||||||
334 | } |
||||||
335 | |||||||
336 | /** |
||||||
337 | * Logs a message. |
||||||
338 | * Messages logged by this method may be retrieved via {@see getLogs}. |
||||||
339 | * @param mixed $token message to be logged |
||||||
340 | * @param int $level level of the message. Valid values include |
||||||
341 | * TLogger::DEBUG, TLogger::INFO, TLogger::NOTICE, TLogger::WARNING, |
||||||
342 | * TLogger::ERROR, TLogger::ALERT, TLogger::FATAL, TLogger::PROFILE, |
||||||
343 | * TLogger::PROFILE_BEGIN, TLogger::PROFILE_END |
||||||
344 | * @param ?string $category category of the message |
||||||
345 | * @param null|string|TControl $ctl control of the message |
||||||
346 | * @return ?float The time delta for PROFILE_END; and null in other cases. |
||||||
347 | */ |
||||||
348 | public function log(mixed $token, int $level, ?string $category = null, mixed $ctl = null): ?float |
||||||
349 | { |
||||||
350 | $this->ensureFlushing(); |
||||||
351 | |||||||
352 | if ($ctl instanceof TControl) { |
||||||
353 | $ctl = $ctl->getClientID(); |
||||||
354 | } elseif (!is_string($ctl)) { |
||||||
355 | $ctl = null; |
||||||
356 | } |
||||||
357 | |||||||
358 | if ($category === null) { |
||||||
359 | $category = Prado::callingObject()::class; |
||||||
360 | } |
||||||
361 | |||||||
362 | $traces = null; |
||||||
363 | if ($this->_traceLevel > 0) { |
||||||
364 | |||||||
365 | $traces = []; |
||||||
366 | $count = 0; |
||||||
367 | $allTraces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); |
||||||
368 | array_pop($allTraces); // remove the last trace since it would be the entry script, not very useful |
||||||
369 | |||||||
370 | foreach ($allTraces as $trace) { |
||||||
371 | if (isset($trace['file'], $trace['line']) && strpos($trace['file'], PRADO_DIR) !== 0) { |
||||||
372 | |||||||
373 | unset($trace['object'], $trace['args']); |
||||||
374 | $traces[] = $trace; |
||||||
375 | |||||||
376 | if (++$count >= $this->_traceLevel) { |
||||||
377 | break; |
||||||
378 | } |
||||||
379 | } |
||||||
380 | } |
||||||
381 | } |
||||||
382 | |||||||
383 | return $this->addLog([$token, $level, $category, microtime(true), memory_get_usage(), $ctl, $traces, getmypid()]); |
||||||
384 | } |
||||||
385 | |||||||
386 | |||||||
387 | /** |
||||||
388 | * This event collects any logs from other aspects of the application that may |
||||||
389 | * not be able to directly log to the TLogger. |
||||||
390 | * @param bool $final Is the final collection, default false. |
||||||
391 | * @since 4.3.0 |
||||||
392 | */ |
||||||
393 | public function onCollectLogs(bool $final = false) |
||||||
394 | { |
||||||
395 | return $this->raiseEvent('onCollectLogs', $this, $final); |
||||||
0 ignored issues
–
show
$final of type boolean is incompatible with the type Prado\TEventParameter expected by parameter $param of Prado\TComponent::raiseEvent() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
396 | } |
||||||
397 | |||||||
398 | /** |
||||||
399 | * This is an Event and an event handler. When {@see Application::onEndRequest()} |
||||||
400 | * is raised and as a `register_shutdown_function` function, this is called. |
||||||
401 | * On calling, this raises the event `onFlushLogs`. |
||||||
402 | * |
||||||
403 | * @param mixed $sender the sender of the raised event. |
||||||
404 | * @param mixed $final true on `register_shutdown_function`. |
||||||
405 | * @since 4.3.0 |
||||||
406 | */ |
||||||
407 | public function onFlushLogs(mixed $sender = null, mixed $final = null) |
||||||
408 | { |
||||||
409 | if ($this->_flushing) { |
||||||
410 | return; |
||||||
411 | } |
||||||
412 | |||||||
413 | if (is_bool($sender)) { |
||||||
414 | $final = $sender; |
||||||
415 | $sender = null; |
||||||
0 ignored issues
–
show
|
|||||||
416 | } |
||||||
417 | if (!is_bool($final)) { |
||||||
418 | $final = false; |
||||||
419 | } |
||||||
420 | $this->onCollectLogs($final); |
||||||
421 | |||||||
422 | $this->_flushing = true; |
||||||
423 | $this->_flushingLog = []; |
||||||
424 | $this->raiseEvent('onFlushLogs', $this, $final); |
||||||
0 ignored issues
–
show
$final of type boolean is incompatible with the type Prado\TEventParameter expected by parameter $param of Prado\TComponent::raiseEvent() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
425 | $this->deleteLogs(); |
||||||
426 | $this->_flushing = false; |
||||||
427 | if (count($this->_flushingLog)) { |
||||||
0 ignored issues
–
show
It seems like
$this->_flushingLog can also be of type null ; however, parameter $value of count() does only seem to accept Countable|array , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
428 | foreach ($this->_flushingLog as $log) { |
||||||
429 | $this->addLog($log, false); // $final = false to stop any possible recursion w/ low flushCount. |
||||||
430 | } |
||||||
431 | } |
||||||
432 | $this->_flushingLog = null; |
||||||
433 | |||||||
434 | $this->_profileLogsCount = 0; |
||||||
435 | foreach (array_keys($this->_profileLogs) as $key) { |
||||||
436 | $this->_profileLogs[$key][static::LOG_LEVEL] |= static::LOGGED; |
||||||
437 | } |
||||||
438 | } |
||||||
439 | |||||||
440 | /** |
||||||
441 | * Retrieves log messages. |
||||||
442 | * Messages may be filtered by log levels and/or categories and/or control client ids and/or timestamp. |
||||||
443 | * A level filter is specified by an integer, whose bits indicate the levels interested. |
||||||
444 | * For example, (TLogger::INFO | TLogger::WARNING) specifies INFO and WARNING levels. |
||||||
445 | * A category filter is specified by an array of categories to filter. |
||||||
446 | * A message whose category name starts with any filtering category |
||||||
447 | * will be returned. For example, a category filter array('Prado\Web','Prado\IO') |
||||||
448 | * will return messages under categories such as 'Prado\Web', 'Prado\IO', |
||||||
449 | * 'Prado\Web\UI', 'Prado\Web\UI\WebControls', etc. |
||||||
450 | * A control client id filter is specified by an array of control client id |
||||||
451 | * A message whose control client id starts with any filtering naming panels |
||||||
452 | * will be returned. For example, a category filter array('ctl0_body_header', |
||||||
453 | * 'ctl0_body_content_sidebar') |
||||||
454 | * will return messages under categories such as 'ctl0_body_header', 'ctl0_body_content_sidebar', |
||||||
455 | * 'ctl0_body_header_title', 'ctl0_body_content_sidebar_savebutton', etc. |
||||||
456 | * A timestamp filter is specified as an interger or float number. |
||||||
457 | * A message whose registered timestamp is less or equal the filter value will be returned. |
||||||
458 | * Level filter, category filter, control filter and timestamp filter are combinational, i.e., only messages |
||||||
459 | * satisfying all filter conditions will they be returned. |
||||||
460 | * @param ?int $levels level filter |
||||||
461 | * @param null|array|string $categories category filter |
||||||
462 | * @param null|array|string $controls control filter |
||||||
463 | * @param null|float|int $timestamp filter |
||||||
464 | * @param ?int $pid |
||||||
465 | * @return array list of messages. Each array elements represents one message |
||||||
466 | * with the following structure: |
||||||
467 | * array( |
||||||
468 | * [0] => message |
||||||
469 | * [1] => level |
||||||
470 | * [2] => category |
||||||
471 | * [3] => timestamp (by microtime(true), float number) |
||||||
472 | * [4] => memory in bytes |
||||||
473 | * [5] => control client id; null when absent |
||||||
474 | * @ since 4.3.0: |
||||||
475 | * [6] => traces, when configured; null when absent |
||||||
476 | * [7] => process id) |
||||||
477 | */ |
||||||
478 | public function getLogs(?int $levels = null, null|array|string $categories = null, null|array|string $controls = null, null|int|float $timestamp = null, ?int $pid = null) |
||||||
479 | { |
||||||
480 | $this->_levels = $levels; |
||||||
481 | if (!empty($categories) && !is_array($categories)) { |
||||||
0 ignored issues
–
show
|
|||||||
482 | $categories = [$categories]; |
||||||
483 | } |
||||||
484 | $this->_categories = $categories; |
||||||
485 | if (!empty($controls) && !is_array($controls)) { |
||||||
0 ignored issues
–
show
|
|||||||
486 | $controls = [$controls]; |
||||||
487 | } |
||||||
488 | $this->_controls = $controls; |
||||||
489 | $this->_timestamp = $timestamp; |
||||||
490 | $this->_pid = $pid; |
||||||
491 | |||||||
492 | $logs = array_merge($this->_profileLogs, $this->_logs); |
||||||
493 | if (empty($levels) && empty($categories) && empty($controls) && $timestamp === null && $pid === null) { |
||||||
494 | usort($logs, [$this, 'orderByTimeStamp']); |
||||||
495 | return $logs; |
||||||
496 | } |
||||||
497 | |||||||
498 | if (!empty($levels)) { |
||||||
499 | $logs = array_filter($logs, [$this, 'filterByLevels']); |
||||||
500 | } |
||||||
501 | if (!empty($categories)) { |
||||||
502 | $logs = array_filter($logs, [$this, 'filterByCategories']); |
||||||
503 | } |
||||||
504 | if (!empty($controls)) { |
||||||
505 | $logs = array_filter($logs, [$this, 'filterByControl']); |
||||||
506 | } |
||||||
507 | if ($timestamp !== null) { |
||||||
508 | $logs = array_filter($logs, [$this, 'filterByTimeStamp']); |
||||||
509 | } |
||||||
510 | if ($pid !== null) { |
||||||
511 | $logs = array_filter($logs, [$this, 'filterByPID']); |
||||||
512 | } |
||||||
513 | |||||||
514 | usort($logs, [$this, 'orderByTimeStamp']); |
||||||
515 | |||||||
516 | return array_values($logs); |
||||||
517 | } |
||||||
518 | |||||||
519 | /** |
||||||
520 | * This merges a set of logs with the current running logs. |
||||||
521 | * @param array $logs the logs elements to insert. |
||||||
522 | * @since 4.3.0 |
||||||
523 | */ |
||||||
524 | public function mergeLogs(array $logs) |
||||||
525 | { |
||||||
526 | $count = count($logs); |
||||||
527 | foreach ($logs as $log) { |
||||||
528 | $log[static::LOG_LEVEL] &= ~TLogger::LOGGED; |
||||||
529 | $this->addLog($log, !(--$count)); |
||||||
530 | } |
||||||
531 | } |
||||||
532 | |||||||
533 | /** |
||||||
534 | * Deletes log messages from the queue. |
||||||
535 | * Messages may be filtered by log levels and/or categories and/or control client ids and/or timestamp. |
||||||
536 | * A level filter is specified by an integer, whose bits indicate the levels interested. |
||||||
537 | * For example, (TLogger::INFO | TLogger::WARNING) specifies INFO and WARNING levels. |
||||||
538 | * A category filter is specified by an array of categories to filter. |
||||||
539 | * A message whose category name starts with any filtering category |
||||||
540 | * will be deleted. For example, a category filter array('Prado\Web','Prado\IO') |
||||||
541 | * will delete messages under categories such as 'Prado\Web', 'Prado\IO', |
||||||
542 | * 'Prado\Web\UI', 'Prado\Web\UI\WebControls', etc. |
||||||
543 | * A control client id filter is specified by an array of control client id |
||||||
544 | * A message whose control client id starts with any filtering naming panels |
||||||
545 | * will be deleted. For example, a category filter array('ctl0_body_header', |
||||||
546 | * 'ctl0_body_content_sidebar') |
||||||
547 | * will delete messages under categories such as 'ctl0_body_header', 'ctl0_body_content_sidebar', |
||||||
548 | * 'ctl0_body_header_title', 'ctl0_body_content_sidebar_savebutton', etc. |
||||||
549 | * A timestamp filter is specified as an interger or float number. |
||||||
550 | * A message whose registered timestamp is less or equal the filter value will be returned. |
||||||
551 | * Level filter, category filter, control filter and timestamp filter are combinational, i.e., only messages |
||||||
552 | * satisfying all filter conditions will they be returned. |
||||||
553 | * @param null|int $levels level filter |
||||||
554 | * @param null|array|string $categories category filter |
||||||
555 | * @param null|array|string $controls control filter |
||||||
556 | * @param null|float|int $timestamp timestamp filter |
||||||
557 | * @param ?int $pid |
||||||
558 | * @return array The logs being delete are returned. |
||||||
559 | */ |
||||||
560 | public function deleteLogs(?int $levels = null, null|string|array $categories = null, null|string|array $controls = null, null|int|float $timestamp = null, ?int $pid = null): array |
||||||
561 | { |
||||||
562 | $this->_levels = $levels; |
||||||
563 | if (!empty($categories) && !is_array($categories)) { |
||||||
0 ignored issues
–
show
|
|||||||
564 | $categories = [$categories]; |
||||||
565 | } |
||||||
566 | $this->_categories = $categories; |
||||||
567 | if (!empty($controls) && !is_array($controls)) { |
||||||
0 ignored issues
–
show
|
|||||||
568 | $controls = [$controls]; |
||||||
569 | } |
||||||
570 | $this->_controls = $controls; |
||||||
571 | $this->_timestamp = $timestamp; |
||||||
572 | $this->_pid = $pid; |
||||||
573 | |||||||
574 | if (empty($levels) && empty($categories) && empty($controls) && $timestamp === null && $pid === null) { |
||||||
575 | $logs = $this->_logs; |
||||||
576 | $this->_logs = []; |
||||||
577 | return $logs; |
||||||
578 | } |
||||||
579 | $logs = $this->_logs; |
||||||
580 | if (!empty($levels)) { |
||||||
581 | $logs = (array_filter($logs, [$this, 'filterByLevels'])); |
||||||
582 | } |
||||||
583 | if (!empty($categories)) { |
||||||
584 | $logs = (array_filter($logs, [$this, 'filterByCategories'])); |
||||||
585 | } |
||||||
586 | if (!empty($controls)) { |
||||||
587 | $logs = (array_filter($logs, [$this, 'filterByControl'])); |
||||||
588 | } |
||||||
589 | if ($timestamp !== null) { |
||||||
590 | $logs = (array_filter($logs, [$this, 'filterByTimeStamp'])); |
||||||
591 | } |
||||||
592 | if ($pid !== null) { |
||||||
593 | $logs = array_filter($logs, [$this, 'filterByPID']); |
||||||
594 | } |
||||||
595 | $this->_logs = array_values(array_diff_key($this->_logs, $logs)); |
||||||
596 | |||||||
597 | return array_values($logs); |
||||||
598 | } |
||||||
599 | |||||||
600 | /** |
||||||
601 | * Deletes the retained Profile Begin logs. |
||||||
602 | * @return array The deleted Profile Begin Logs. |
||||||
603 | * @since 4.3.0 |
||||||
604 | */ |
||||||
605 | public function deleteProfileLogs(): array |
||||||
606 | { |
||||||
607 | $profileLogs = array_values($this->_profileLogs); |
||||||
608 | $this->_profileLogs = []; |
||||||
609 | $this->_profileLogsCount = 0; |
||||||
610 | $this->_profileBeginTimes = []; |
||||||
611 | |||||||
612 | return $profileLogs; |
||||||
613 | } |
||||||
614 | |||||||
615 | /** |
||||||
616 | * Order function used by {@see static::getLogs()}. |
||||||
617 | * @param mixed $a First element to compare. |
||||||
618 | * @param mixed $b Second element to compare. |
||||||
619 | * @return int The order between the two elements. |
||||||
620 | * @since 4.3.0 |
||||||
621 | */ |
||||||
622 | private function orderByTimeStamp($a, $b): int |
||||||
623 | { |
||||||
624 | return ($a[static::LOG_TIME] != $b[static::LOG_TIME]) ? ($a[static::LOG_TIME] <= $b[static::LOG_TIME]) ? -1 : 1 : 0; |
||||||
625 | } |
||||||
626 | |||||||
627 | /** |
||||||
628 | * Filter function used by {@see static::getLogs()}. |
||||||
629 | * @param array $log element to be filtered |
||||||
630 | * @return bool retain the element. |
||||||
631 | */ |
||||||
632 | private function filterByCategories($log): bool |
||||||
633 | { |
||||||
634 | $open = true; |
||||||
635 | $match = false; |
||||||
636 | $exclude = false; |
||||||
637 | |||||||
638 | foreach ($this->_categories as $category) { |
||||||
639 | if (empty($category)) { |
||||||
640 | $category = ''; |
||||||
641 | } |
||||||
642 | $c = $category[0] ?? 0; |
||||||
643 | if ($c === '!' || $c === '~') { |
||||||
644 | if (!$exclude) { |
||||||
645 | $category = substr($category, 1); |
||||||
646 | if ($log[static::LOG_CATEGORY] === $category || str_ends_with($category, '*') && strpos($log[static::LOG_CATEGORY], rtrim($category, '*')) === 0) { |
||||||
647 | $exclude = true; |
||||||
648 | } |
||||||
649 | } |
||||||
650 | } elseif (!$match) { |
||||||
651 | $open = false; |
||||||
652 | if (($log[static::LOG_CATEGORY] === $category || str_ends_with($category, '*') && strpos($log[static::LOG_CATEGORY], rtrim($category, '*')) === 0)) { |
||||||
653 | $match = true; |
||||||
654 | } |
||||||
655 | } |
||||||
656 | |||||||
657 | if ($match && $exclude) { |
||||||
658 | break; |
||||||
659 | } |
||||||
660 | } |
||||||
661 | |||||||
662 | return ($open || $match) && !$exclude; |
||||||
663 | } |
||||||
664 | |||||||
665 | /** |
||||||
666 | * Filter function used by {@see static::getLogs()} |
||||||
667 | * @param array $log element to be filtered |
||||||
668 | * @return bool retain the element. |
||||||
669 | */ |
||||||
670 | private function filterByLevels($log): bool |
||||||
671 | { |
||||||
672 | // element 1 are the levels |
||||||
673 | if ($log[static::LOG_LEVEL] & $this->_levels) { |
||||||
674 | return true; |
||||||
675 | } else { |
||||||
676 | return false; |
||||||
677 | } |
||||||
678 | } |
||||||
679 | |||||||
680 | /** |
||||||
681 | * Filter function used by {@see static::getLogs()} |
||||||
682 | * @param array $log element to be filtered |
||||||
683 | * @return bool retain the element. |
||||||
684 | */ |
||||||
685 | private function filterByControl($log): bool |
||||||
686 | { |
||||||
687 | $open = true; |
||||||
688 | $match = false; |
||||||
689 | $exclude = false; |
||||||
690 | |||||||
691 | if (empty($log[static::LOG_CONTROL])) { |
||||||
692 | $log[static::LOG_CONTROL] = ''; |
||||||
693 | } |
||||||
694 | |||||||
695 | foreach ($this->_controls as $control) { |
||||||
696 | if (empty($control)) { |
||||||
697 | $control = ''; |
||||||
698 | } |
||||||
699 | $c = $control[0] ?? 0; |
||||||
700 | if ($c === '!' || $c === '~') { |
||||||
701 | if (!$exclude) { |
||||||
702 | $control = substr($control, 1); |
||||||
703 | if ($log[static::LOG_CONTROL] === $control || str_ends_with($control, '*') && strpos($log[static::LOG_CONTROL], rtrim($control, '*')) === 0) { |
||||||
704 | $exclude = true; |
||||||
705 | } |
||||||
706 | } |
||||||
707 | } elseif (!$match) { |
||||||
708 | $open = false; |
||||||
709 | if (($log[static::LOG_CONTROL] === $control || str_ends_with($control, '*') && strpos($log[static::LOG_CONTROL], rtrim($control, '*')) === 0)) { |
||||||
710 | $match = true; |
||||||
711 | } |
||||||
712 | } |
||||||
713 | if ($match && $exclude) { |
||||||
714 | break; |
||||||
715 | } |
||||||
716 | } |
||||||
717 | |||||||
718 | return ($open || $match) && !$exclude; |
||||||
719 | } |
||||||
720 | |||||||
721 | /** |
||||||
722 | * Filter function used by {@see static::getLogs()} |
||||||
723 | * @param array $log element to be filtered |
||||||
724 | * @return bool retain the element. |
||||||
725 | */ |
||||||
726 | private function filterByTimeStamp($log): bool |
||||||
727 | { |
||||||
728 | if ($log[static::LOG_TIME] <= $this->_timestamp) { |
||||||
729 | return true; |
||||||
730 | } else { |
||||||
731 | return false; |
||||||
732 | } |
||||||
733 | } |
||||||
734 | |||||||
735 | /** |
||||||
736 | * Filter function used by {@see static::getLogs()} |
||||||
737 | * @param array $log element to be filtered |
||||||
738 | * @return bool retain the element. |
||||||
739 | * @since 4.3.0 |
||||||
740 | */ |
||||||
741 | private function filterByPID($log): bool |
||||||
742 | { |
||||||
743 | if ($log[static::LOG_PID] === $this->_pid) { |
||||||
744 | return true; |
||||||
745 | } else { |
||||||
746 | return false; |
||||||
747 | } |
||||||
748 | } |
||||||
749 | } |
||||||
750 |