Passed
Push — master ( d979ac...b22592 )
by Fabio
05:10
created

TLogger::addLog()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 36
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
cc 8
eloc 25
nc 10
nop 2
dl 0
loc 36
ccs 0
cts 0
cp 0
crap 72
rs 8.4444
c 0
b 0
f 0
1
<?php
2
/**
3
 * TLogger class file
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 */
9
10
namespace Prado\Util;
11
12
use Prado\Prado;
13
use Prado\TEventParameter;
14
use Prado\Web\UI\TControl;
15
16
/**
17
 * TLogger class.
18
 *
19
 * TLogger records log messages in memory and implements the methods to
20
 * retrieve the messages with filter conditions, including log levels,
21
 * log categories, and by control.
22
 *
23
 * Filtering categories and controls can use a '!' or '~' prefix and the element
24
 * will be excluded rather than included.  Using a trailing '*' will include
25
 * elements starting with the specified text.
26
 *
27
 * The log message, log level, log category (class), microtime Time stamp
28
 * memory used, control ID, traces, and process ID.
29
 *
30
 * @author Qiang Xue <[email protected]>
31
 * @author Brad Anderson <[email protected]>
32
 * @since 3.0
33
 */
34
class TLogger extends \Prado\TComponent
35
{
36
	/**
37
	 * Log levels.
38
	 */
39
	public const DEBUG = 0x01;
40
	public const INFO = 0x02;
41
	public const NOTICE = 0x04;
42
	public const WARNING = 0x08;
43
	public const ERROR = 0x10;
44
	public const ALERT = 0x20;
45
	public const FATAL = 0x40;
46
47
	public const PROFILE = 0x100;
48
	public const PROFILE_BEGIN = 0x300;
49
	public const PROFILE_END = 0x500;
50
51
	/**
52
	 * These are for selecting just the PROFILE_BEGIN and PROFILE_END logs without
53
	 * the PROFILE bit.
54
	 */
55
	public const PROFILE_BEGIN_SELECT = 0x200;
56
	public const PROFILE_END_SELECT = 0x400;
57
58
	public const LOGGED = 0x8000;
59
60
	/**
61
	 * Log Information Index.
62
	 */
63
	public const LOG_MESSAGE = 0;
64
	public const LOG_LEVEL = 1;
65
	public const LOG_CATEGORY = 2;
66
	public const LOG_TIME = 3;
67
	public const LOG_MEMORY = 4;
68
	public const LOG_CONTROL = 5;
69 32
	public const LOG_TRACES = 6;
70
	public const LOG_PID = 7;
71 32
72
	/**
73
	 * @var array log messages
74
	 */
75
	private array $_logs = [];
76
	/**
77
	 * @var array unmatched PROFILE_BEGIN log messages
78 32
	 * @since 4.2.3
79
	 */
80 32
	private array $_profileLogs = [];
81 32
	/**
82
	 * @var int the profileLogs count that are not static::LOGGED.
83
	 * @since 4.2.3
84
	 */
85
	private int $_profileLogsCount = 0;
86
	/**
87
	 * @var array The maintained Profile Begin times
88
	 * @since 4.2.3
89
	 */
90
	private array $_profileBeginTimes = [];
91
	/**
92
	 * @var ?int log levels (bits) to be filtered
93
	 */
94
	private ?int $_levels = null;
95
	/**
96
	 * @var ?array list of categories to be filtered
97
	 */
98
	private ?array $_categories = null;
99
	/**
100
	 * @var array list of control client ids to be filtered
101
	 */
102
	private ?array $_controls = null;
103
	/**
104
	 * @var ?float timestamp used to filter
105
	 */
106
	private ?float $_timestamp;
107
	/**
108
	 * @var ?int process id used to filter
109
	 * @since 4.2.3
110
	 */
111
	private ?int $_pid;
112
113
	/**
114
	 * @var int the number of logs before flushing.
115
	 * @since 4.2.3
116
	 */
117
	private int $_flushCount = 1000;
118
	/**
119
	 * @var bool is the logger flushing.
120
	 * @since 4.2.3
121
	 */
122
	private bool $_flushing = false;
123
	/**
124
	 * @var ?array any logged messages during flushing so they aren't flushed.
125
	 * @since 4.2.3
126
	 */
127
	private ?array $_flushingLog = null;
128
	/**
129
	 * @var int The depth of a trace, default 0 for no trace.
130
	 * @since 4.2.3
131
	 */
132
	private int $_traceLevel = 0;
133
	/**
134
	 * @var bool Is the logger Registered with onEndRequest
135
	 * @since 4.2.3
136
	 */
137
	private bool $_registered = false;
138
139
	/**
140
	 * @param bool $flushShutdown Should onFlushLogs be a register_shutdown_function.
141
	 * @since 4.2.3
142
	 */
143
	public function __construct(bool $flushShutdown = true)
144
	{
145
		if($flushShutdown) {
146
			register_shutdown_function(function () {
147
				$this->onFlushLogs();
148
				// flush any logs in the shutdown function.
149
				register_shutdown_function([$this, 'onFlushLogs'], $this, true);
150
			});
151
		}
152
		parent::__construct();
153
	}
154
155
	/**
156
	 * @return int The number of logs before triggering {@see self::onFlushLogs()}
157
	 * @since 4.2.3
158
	 */
159
	public function getFlushCount(): int
160
	{
161
		return $this->_flushCount;
162
	}
163
164
	/**
165
	 * @param int $value the number of logs before triggering {@see self::onFlushLogs()}
166
	 * @return static $this
167
	 * @since 4.2.3
168
	 */
169
	public function setFlushCount(int $value): static
170
	{
171
		$this->_flushCount = $value;
172
173
		$this->checkProfileLogsSize();
174
		$this->checkLogsSize();
175
176
		return $this;
177
	}
178
179
	/**
180
	 * @return int How much debug trace stack information to include.
181
	 * @since 4.2.3
182
	 */
183
	public function getTraceLevel(): int
184
	{
185
		return $this->_traceLevel;
186
	}
187
188
	/**
189
	 * @param int $value How much debug trace stack information to include.
190
	 * @return static $this
191
	 * @since 4.2.3
192
	 */
193
	public function setTraceLevel(int $value): static
194
	{
195
		$this->_traceLevel = $value;
196
197
		return $this;
198
	}
199
200
	/**
201
	 * This returns the number of logs being held.  When the $selector the following
202
	 * is returned:
203
	 *   - null is the ~ number of logs a route will receive
204
	 *   - 0 is the ~ number of logs to be collected before removing static::LOGGGED logs
205
	 *   - true is the number of logs without profile begin
206
	 *   - false is the number of profile begin logs
207
	 *
208
	 * When a PROFILE_BEGIN is flushed without a paired PROFILE_END, then it is logged
209
	 * and flagged as static::LOGGED.  These log items will be resent until and with
210
	 * a paired PROFILE_END.  The PROFILE_BEGIN are relogged to computing timing but are
211
	 * removed before being processed by the log route.
212
	 *
213
	 * @param ?bool $selector Which list of logs to count. Default null for both.
214
	 * @return int The number of logs.
215
	 * @since 4.2.3
216
	 */
217
	public function getLogCount(null|bool|int $selector = null): int
218
	{
219
		if ($selector === null) {// logs @TLogRoute::processLogs after removing logged elements.
220
			return count($this->_logs) + $this->_profileLogsCount;
221
		} elseif ($selector === 0) {// logs @TLogRoute::collectLogs, from TLogger::getLogs
222
			return count($this->_logs) + count($this->_profileLogs);
223
		} elseif ($selector) {
224
			return count($this->_logs);
225
		} //elseif false
226
		return count($this->_profileLogs);
227
	}
228
229
	/**
230
	 * This is the number of Profile Begin Logs that have been logged.
231
	 * @return int The Profile Logs already logged.
232
	 * @since 4.2.3
233
	 */
234
	public function getLoggedProfileLogCount(): int
235
	{
236
		return count($this->_profileLogs) - $this->_profileLogsCount;
237
	}
238
239
	/**
240
	 * Ensures that the logger is registered with the Application onEndRequest.
241
	 * @since 4.2.3
242
	 */
243
	protected function ensureFlushing()
244
	{
245
		if ($this->_registered) {
246
			return;
247
		}
248
249
		if ($app = Prado::getApplication()) {
250
			$app->attachEventHandler('onEndRequest', [$this, 'onFlushLogs'], 20);
0 ignored issues
show
Bug introduced by
20 of type integer is incompatible with the type Prado\numeric|null expected by parameter $priority of Prado\TComponent::attachEventHandler(). ( Ignorable by Annotation )

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

250
			$app->attachEventHandler('onEndRequest', [$this, 'onFlushLogs'], /** @scrutinizer ignore-type */ 20);
Loading history...
251
			//$app->attachEventHandler(TProcessHelper::FX_TERMINATE_PROCESS, [$this, 'onFlushLogs'], 20);
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.2.3
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
Bug introduced by
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 ignore-call  annotation

271
			/** @scrutinizer ignore-call */ 
272
   $profileName = $this->dyProfilerRename('[' . $log[static::LOG_MESSAGE] . '][' . $log[static::LOG_PID] . ']');
Loading history...
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.2.3
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.2.3
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 {@link 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 is an Event and an event handler.  When {@see Application::onEndRequest()}
389
	 * is raised and as a `register_shutdown_function` function, this is called.
390
	 * On calling, this raises the event `onFlushLogs`.
391
	 *
392
	 * @param mixed $sender the sender of the raised event.
393
	 * @param mixed $final true on `register_shutdown_function`.
394
	 * @since 4.2.3
395
	 */
396
	public function onFlushLogs(mixed $sender = null, mixed $final = null)
397
	{
398
		if ($this->_flushing || !$final && !count($this->_logs)) {
399
			return;
400
		}
401
402
		if(is_bool($sender)) {
403
			$final = $sender;
404
			$sender = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $sender is dead and can be removed.
Loading history...
405
		}
406
		//if (($final instanceof TEventParameter) && $final->getEventName() === TProcessHelper::FX_TERMINATE_PROCESS) {
407
		//	$final = true;
408
		//} else
409
		if (!is_bool($final)) {
410
			$final = false;
411
		}
412
413
		$this->_flushing = true;
414
		$this->_flushingLog = [];
415
		$this->raiseEvent('onFlushLogs', $this, $final);
0 ignored issues
show
Bug introduced by
$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 ignore-type  annotation

415
		$this->raiseEvent('onFlushLogs', $this, /** @scrutinizer ignore-type */ $final);
Loading history...
416
		$this->deleteLogs();
417
		$this->_flushing = false;
418
		if (count($this->_flushingLog)) {
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

418
		if (count(/** @scrutinizer ignore-type */ $this->_flushingLog)) {
Loading history...
419
			foreach($this->_flushingLog as $log) {
420
				$this->addLog($log, false); // $final = false to stop any possible recursion w/ low flushCount.
421
			}
422
		}
423
		$this->_flushingLog = null;
424
425
		$this->_profileLogsCount = 0;
426
		foreach(array_keys($this->_profileLogs) as $key) {
427
			$this->_profileLogs[$key][static::LOG_LEVEL] |= static::LOGGED;
428
		}
429
	}
430
431
	/**
432
	 * Retrieves log messages.
433
	 * Messages may be filtered by log levels and/or categories and/or control client ids and/or timestamp.
434
	 * A level filter is specified by an integer, whose bits indicate the levels interested.
435
	 * For example, (TLogger::INFO | TLogger::WARNING) specifies INFO and WARNING levels.
436
	 * A category filter is specified by an array of categories to filter.
437
	 * A message whose category name starts with any filtering category
438
	 * will be returned. For example, a category filter array('Prado\Web','Prado\IO')
439
	 * will return messages under categories such as 'Prado\Web', 'Prado\IO',
440
	 * 'Prado\Web\UI', 'Prado\Web\UI\WebControls', etc.
441
	 * A control client id filter is specified by an array of control client id
442
	 * A message whose control client id starts with any filtering naming panels
443
	 * will be returned. For example, a category filter array('ctl0_body_header',
444
	 * 'ctl0_body_content_sidebar')
445
	 * will return messages under categories such as 'ctl0_body_header', 'ctl0_body_content_sidebar',
446
	 * 'ctl0_body_header_title', 'ctl0_body_content_sidebar_savebutton', etc.
447
	 * A timestamp filter is specified as an interger or float number.
448
	 * A message whose registered timestamp is less or equal the filter value will be returned.
449
	 * Level filter, category filter, control filter and timestamp filter are combinational, i.e., only messages
450
	 * satisfying all filter conditions will they be returned.
451
	 * @param ?int $levels level filter
452
	 * @param null|array|string $categories category filter
453
	 * @param null|array|string $controls control filter
454
	 * @param null|float|int $timestamp filter
455
	 * @param ?int $pid
456
	 * @return array list of messages. Each array elements represents one message
457
	 * with the following structure:
458
	 * array(
459
	 *   [0] => message
460
	 *   [1] => level
461
	 *   [2] => category
462
	 *   [3] => timestamp (by microtime(true), float number)
463
	 *   [4] => memory in bytes
464
	 *   [5] => control client id; null when absent
465
	 *      @ since 4.2.3:
466
	 *   [6] => traces, when configured; null when absent
467
	 *   [7] => process id)
468
	 */
469
	public function getLogs(?int $levels = null, null|array|string $categories = null, null|array|string $controls = null, null|int|float $timestamp = null, ?int $pid = null)
470
	{
471
		$this->_levels = $levels;
472
		if (!empty($categories) && !is_array($categories)) {
0 ignored issues
show
introduced by
The condition is_array($categories) is always true.
Loading history...
473
			$categories = [$categories];
474
		}
475
		$this->_categories = $categories;
476
		if (!empty($controls) && !is_array($controls)) {
0 ignored issues
show
introduced by
The condition is_array($controls) is always true.
Loading history...
477
			$controls = [$controls];
478
		}
479
		$this->_controls = $controls;
480
		$this->_timestamp = $timestamp;
481
		$this->_pid = $pid;
482
483
		$logs = array_merge($this->_profileLogs, $this->_logs);
484
		if (empty($levels) && empty($categories) && empty($controls) && $timestamp === null && $pid === null) {
485
			usort($logs, [$this, 'orderByTimeStamp']);
486
			return $logs;
487
		}
488
489
		if (!empty($levels)) {
490
			$logs = array_filter($logs, [$this, 'filterByLevels']);
491
		}
492
		if (!empty($categories)) {
493
			$logs = array_filter($logs, [$this, 'filterByCategories']);
494
		}
495
		if (!empty($controls)) {
496
			$logs = array_filter($logs, [$this, 'filterByControl']);
497
		}
498
		if ($timestamp !== null) {
499
			$logs = array_filter($logs, [$this, 'filterByTimeStamp']);
500
		}
501
		if ($pid !== null) {
502
			$logs = array_filter($logs, [$this, 'filterByPID']);
503
		}
504
505
		usort($logs, [$this, 'orderByTimeStamp']);
506
507
		return array_values($logs);
508
	}
509
510
	/**
511
	 * This merges a set of logs with the current running logs.
512
	 * @param array $logs the logs elements to insert.
513
	 * @since 4.2.3
514
	 */
515
	public function mergeLogs(array $logs)
516
	{
517
		$count = count($logs);
518
		foreach($logs as $log) {
519
			$log[static::LOG_LEVEL] &= ~TLogger::LOGGED;
520
			$this->addLog($log, !(--$count));
521
		}
522
	}
523
524
	/**
525
	 * Deletes log messages from the queue.
526
	 * Messages may be filtered by log levels and/or categories and/or control client ids and/or timestamp.
527
	 * A level filter is specified by an integer, whose bits indicate the levels interested.
528
	 * For example, (TLogger::INFO | TLogger::WARNING) specifies INFO and WARNING levels.
529
	 * A category filter is specified by an array of categories to filter.
530
	 * A message whose category name starts with any filtering category
531
	 * will be deleted. For example, a category filter array('Prado\Web','Prado\IO')
532
	 * will delete messages under categories such as 'Prado\Web', 'Prado\IO',
533
	 * 'Prado\Web\UI', 'Prado\Web\UI\WebControls', etc.
534
	 * A control client id filter is specified by an array of control client id
535
	 * A message whose control client id starts with any filtering naming panels
536
	 * will be deleted. For example, a category filter array('ctl0_body_header',
537
	 * 'ctl0_body_content_sidebar')
538
	 * will delete messages under categories such as 'ctl0_body_header', 'ctl0_body_content_sidebar',
539
	 * 'ctl0_body_header_title', 'ctl0_body_content_sidebar_savebutton', etc.
540
	 * A timestamp filter is specified as an interger or float number.
541
	 * A message whose registered timestamp is less or equal the filter value will be returned.
542
	 * Level filter, category filter, control filter and timestamp filter are combinational, i.e., only messages
543
	 * satisfying all filter conditions will they be returned.
544
	 * @param null|int $levels level filter
545
	 * @param null|array|string $categories category filter
546
	 * @param null|array|string $controls control filter
547
	 * @param null|float|int $timestamp timestamp filter
548
	 * @param ?int $pid
549
	 * @return array The logs being delete are returned.
550
	 */
551
	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
552
	{
553
		$this->_levels = $levels;
554
		if (!empty($categories) && !is_array($categories)) {
0 ignored issues
show
introduced by
The condition is_array($categories) is always true.
Loading history...
555
			$categories = [$categories];
556
		}
557
		$this->_categories = $categories;
558
		if (!empty($controls) && !is_array($controls)) {
0 ignored issues
show
introduced by
The condition is_array($controls) is always true.
Loading history...
559
			$controls = [$controls];
560
		}
561
		$this->_controls = $controls;
562
		$this->_timestamp = $timestamp;
563
		$this->_pid = $pid;
564
565
		if (empty($levels) && empty($categories) && empty($controls) && $timestamp === null && $pid === null) {
566
			$logs = $this->_logs;
567
			$this->_logs = [];
568
			return $logs;
569
		}
570
		$logs = $this->_logs;
571
		if (!empty($levels)) {
572
			$logs = (array_filter($logs, [$this, 'filterByLevels']));
573
		}
574
		if (!empty($categories)) {
575
			$logs = (array_filter($logs, [$this, 'filterByCategories']));
576
		}
577
		if (!empty($controls)) {
578
			$logs = (array_filter($logs, [$this, 'filterByControl']));
579
		}
580
		if ($timestamp !== null) {
581
			$logs = (array_filter($logs, [$this, 'filterByTimeStamp']));
582
		}
583
		if ($pid !== null) {
584
			$logs = array_filter($logs, [$this, 'filterByPID']);
585
		}
586
		$this->_logs = array_values(array_diff_key($this->_logs, $logs));
587
588
		return array_values($logs);
589
	}
590
591
	/**
592
	 * Deletes the retained Profile Begin logs.
593
	 * @return array The deleted Profile Begin Logs.
594
	 * @since 4.2.3
595
	 */
596
	public function deleteProfileLogs(): array
597
	{
598
		$profileLogs = array_values($this->_profileLogs);
599
		$this->_profileLogs = [];
600
		$this->_profileLogsCount = 0;
601
		$this->_profileBeginTimes = [];
602
603
		return $profileLogs;
604
	}
605
606
	/**
607
	 * Order function used by {@see static::getLogs()}.
608
	 * @param mixed $a First element to compare.
609
	 * @param mixed $b Second element to compare.
610
	 * @return int The order between the two elements.
611
	 * @since 4.2.3
612
	 */
613
	private function orderByTimeStamp($a, $b): int
614
	{
615
		return ($a[static::LOG_TIME] != $b[static::LOG_TIME]) ? ($a[static::LOG_TIME] <= $b[static::LOG_TIME]) ? -1 : 1 : 0;
616
	}
617
618
	/**
619
	 * Filter function used by {@see static::getLogs()}.
620
	 * @param array $log element to be filtered
621
	 * @return bool retain the element.
622
	 */
623
	private function filterByCategories($log): bool
624
	{
625
		$open = true;
626
		$match = false;
627
		$exclude = false;
628
629
		foreach ($this->_categories as $category) {
630
			if (empty($category)) {
631
				$category = '';
632
			}
633
			$c = $category[0] ?? 0;
634
			if ($c === '!' || $c === '~') {
635
				if(!$exclude) {
636
					$category = substr($category, 1);
637
					if ($log[static::LOG_CATEGORY] === $category || str_ends_with($category, '*') && strpos($log[static::LOG_CATEGORY], rtrim($category, '*')) === 0) {
638
						$exclude = true;
639
					}
640
				}
641
			} elseif (!$match) {
642
				$open = false;
643
				if (($log[static::LOG_CATEGORY] === $category || str_ends_with($category, '*') && strpos($log[static::LOG_CATEGORY], rtrim($category, '*')) === 0)) {
644
					$match = true;
645
				}
646
			}
647
648
			if ($match && $exclude) {
649
				break;
650
			}
651
		}
652
653
		return ($open || $match) && !$exclude;
654
	}
655
656
	/**
657
	 * Filter function used by {@see static::getLogs()}
658
	 * @param array $log element to be filtered
659
	 * @return bool retain the element.
660
	 */
661
	private function filterByLevels($log): bool
662
	{
663
		// element 1 are the levels
664
		if ($log[static::LOG_LEVEL] & $this->_levels) {
665
			return true;
666
		} else {
667
			return false;
668
		}
669
	}
670
671
	/**
672
	 * Filter function used by {@see static::getLogs()}
673
	 * @param array $log element to be filtered
674
	 * @return bool retain the element.
675
	 */
676
	private function filterByControl($log): bool
677
	{
678
		$open = true;
679
		$match = false;
680
		$exclude = false;
681
682
		if (empty($log[static::LOG_CONTROL])) {
683
			$log[static::LOG_CONTROL] = '';
684
		}
685
686
		foreach ($this->_controls as $control) {
687
			if (empty($control)) {
688
				$control = '';
689
			}
690
			$c = $control[0] ?? 0;
691
			if ($c === '!' || $c === '~') {
692
				if(!$exclude) {
693
					$control = substr($control, 1);
694
					if ($log[static::LOG_CONTROL] === $control || str_ends_with($control, '*') && strpos($log[static::LOG_CONTROL], rtrim($control, '*')) === 0) {
695
						$exclude = true;
696
					}
697
				}
698
			} elseif (!$match) {
699
				$open = false;
700
				if (($log[static::LOG_CONTROL] === $control || str_ends_with($control, '*') && strpos($log[static::LOG_CONTROL], rtrim($control, '*')) === 0)) {
701
					$match = true;
702
				}
703
			}
704
			if ($match && $exclude) {
705
				break;
706
			}
707
		}
708
709
		return ($open || $match) && !$exclude;
710
	}
711
712
	/**
713
	 * Filter function used by {@see static::getLogs()}
714
	 * @param array $log element to be filtered
715
	 * @return bool retain the element.
716
	 */
717
	private function filterByTimeStamp($log): bool
718
	{
719
		if ($log[static::LOG_TIME] <= $this->_timestamp) {
720
			return true;
721
		} else {
722
			return false;
723
		}
724
	}
725
726
	/**
727
	 * Filter function used by {@see static::getLogs()}
728
	 * @param array $log element to be filtered
729
	 * @return bool retain the element.
730
	 * @since 4.2.3
731
	 */
732
	private function filterByPID($log): bool
733
	{
734
		if ($log[static::LOG_PID] === $this->_pid) {
735
			return true;
736
		} else {
737
			return false;
738
		}
739
	}
740
}
741