Passed
Pull Request — master (#986)
by
unknown
06:07
created

TLogger::onCollectLogs()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
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;
0 ignored issues
show
Bug introduced by
The type Prado\Prado 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...
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);
251
			$this->_registered = true;
252
		}
253
	}
254
255
	/**
256
	 * Adds a single log item
257
	 * @param array $log The log item to log.
258
	 * @param bool $flush Allow a flush on adding if the log size is FlushCount, default true.
259
	 * @return ?float The time delta for PROFILE_END; and null in other cases.
260
	 * @since 4.2.3
261
	 */
262
	protected function addLog(array $log, bool $flush = true): ?float
263
	{
264
		$return = null;
265
		if ($this->_flushing) {
266
			$this->_flushingLog[] = $log;
267
			return $return;
268
		}
269
		if ($log[static::LOG_LEVEL] & TLogger::PROFILE_BEGIN_SELECT) {
270
			$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

270
			/** @scrutinizer ignore-call */ 
271
   $profileName = $this->dyProfilerRename('[' . $log[static::LOG_MESSAGE] . '][' . $log[static::LOG_PID] . ']');
Loading history...
271
			$add = !isset($this->_profileLogs[$profileName]);
272
			$this->_profileLogs[$profileName] = $log;
273
			$this->_profileBeginTimes[$profileName] = $log[static::LOG_TIME];
274
			if ($add) {
275
				$this->_profileLogsCount++;
276
				$this->checkProfileLogsSize($flush);
277
			}
278
		} else {
279
			if ($log[static::LOG_LEVEL] & TLogger::PROFILE_END_SELECT) { // Move ProfileBegin to the primary log.
280
				$profileName = $this->dyProfilerRename('[' . $log[static::LOG_MESSAGE] . '][' . $log[static::LOG_PID] . ']');
281
				if (isset($this->_profileLogs[$profileName])) {
282
					$this->_logs[] = $this->_profileLogs[$profileName];
283
284
					if (!($this->_profileLogs[$profileName][static::LOG_LEVEL] & static::LOGGED)) {
285
						$this->_profileLogsCount--;
286
					}
287
288
					unset($this->_profileLogs[$profileName]);
289
				}
290
				if (isset($this->_profileBeginTimes[$profileName])) {
291
					$return = $log[static::LOG_TIME] - $this->_profileBeginTimes[$profileName];
292
				}
293
			}
294
			$this->_logs[] = $log;
295
			$this->checkLogsSize($flush);
296
		}
297
		return $return;
298
	}
299
300
	/**
301
	 * When the ProfileLog Count is equal or more than the FlushCount, then the Retained
302
	 * profile logs are deleted and a WARNING is logged.
303
	 * @param bool $flush Is the log being flushing, default true.
304
	 * @since 4.2.3
305
	 */
306
	protected function checkProfileLogsSize(bool $flush = true)
307
	{
308
		if ($this->_flushCount && $this->getLogCount(false) >= $this->_flushCount) {
309
			$this->deleteProfileLogs();
310
			$saved = null;
311
			if (!$flush) {
312
				$saved = $this->_flushCount;
313
				$this->_flushCount = 0;
314
			}
315
			$this->log("Too many retained profiler logs", static::WARNING, self::class . '\Profiler');
316
			if (!$flush) {
317
				$this->_flushCount = $saved;
318
			}
319
		}
320
	}
321
322
	/**
323
	 * If we allow a flush (not a bulk logging), and the log count is equal to or more
324
	 * than the FlushCount then we flush the logs.
325
	 * @param bool $flush Is the log being flushing, default true.
326
	 * @since 4.2.3
327
	 */
328
	protected function checkLogsSize(bool $flush = true)
329
	{
330
		if ($flush && $this->_flushCount && $this->getLogCount() >= $this->_flushCount) {
331
			$this->onFlushLogs();
332
		}
333
	}
334
335
	/**
336
	 * Logs a message.
337
	 * Messages logged by this method may be retrieved via {@see getLogs}.
338
	 * @param mixed $token message to be logged
339
	 * @param int $level level of the message. Valid values include
340
	 * TLogger::DEBUG, TLogger::INFO, TLogger::NOTICE, TLogger::WARNING,
341
	 * TLogger::ERROR, TLogger::ALERT, TLogger::FATAL, TLogger::PROFILE,
342
	 * TLogger::PROFILE_BEGIN, TLogger::PROFILE_END
343
	 * @param ?string $category category of the message
344
	 * @param null|string|TControl $ctl control of the message
345
	 * @return ?float The time delta for PROFILE_END; and null in other cases.
346
	 */
347
	public function log(mixed $token, int $level, ?string $category = null, mixed $ctl = null): ?float
348
	{
349
		$this->ensureFlushing();
350
351
		if ($ctl instanceof TControl) {
352
			$ctl = $ctl->getClientID();
353
		} elseif (!is_string($ctl)) {
354
			$ctl = null;
355
		}
356
357
		if ($category === null) {
358
			$category = Prado::callingObject()::class;
359
		}
360
361
		$traces = null;
362
		if ($this->_traceLevel > 0) {
363
364
			$traces = [];
365
			$count = 0;
366
			$allTraces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
367
			array_pop($allTraces); // remove the last trace since it would be the entry script, not very useful
368
369
			foreach ($allTraces as $trace) {
370
				if (isset($trace['file'], $trace['line']) && strpos($trace['file'], PRADO_DIR) !== 0) {
0 ignored issues
show
Bug introduced by
The constant Prado\Util\PRADO_DIR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
371
372
					unset($trace['object'], $trace['args']);
373
					$traces[] = $trace;
374
375
					if (++$count >= $this->_traceLevel) {
376
						break;
377
					}
378
				}
379
			}
380
		}
381
382
		return $this->addLog([$token, $level, $category, microtime(true), memory_get_usage(), $ctl, $traces, getmypid()]);
383
	}
384
385
386
	/**
387
	 * This event collects any logs from other aspects of the application that may
388
	 * not be able to directly log to the TLogger.
389
	 * @param bool $final Is the final collection, default false.
390
	 * @since 4.2.3
391
	 */
392
	public function onCollectLogs(bool $final = false)
393
	{
394
		return $this->raiseEvent('onCollectLogs', $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

394
		return $this->raiseEvent('onCollectLogs', $this, /** @scrutinizer ignore-type */ $final);
Loading history...
395
	}
396
397
	/**
398
	 * This is an Event and an event handler.  When {@see Application::onEndRequest()}
399
	 * is raised and as a `register_shutdown_function` function, this is called.
400
	 * On calling, this raises the event `onFlushLogs`.
401
	 *
402
	 * @param mixed $sender the sender of the raised event.
403
	 * @param mixed $final true on `register_shutdown_function`.
404
	 * @since 4.2.3
405
	 */
406
	public function onFlushLogs(mixed $sender = null, mixed $final = null)
407
	{
408
		if ($this->_flushing) {
409
			return;
410
		}
411
412
		if(is_bool($sender)) {
413
			$final = $sender;
414
			$sender = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $sender is dead and can be removed.
Loading history...
415
		}
416
		if (!is_bool($final)) {
417
			$final = false;
418
		}
419
		$this->onCollectLogs($final);
420
421
		$this->_flushing = true;
422
		$this->_flushingLog = [];
423
		$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

423
		$this->raiseEvent('onFlushLogs', $this, /** @scrutinizer ignore-type */ $final);
Loading history...
424
		$this->deleteLogs();
425
		$this->_flushing = false;
426
		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

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