Bearsampp
2026.7.11
Toggle main menu visibility
Loading...
Searching...
No Matches
class.log.php
Go to the documentation of this file.
1
<?php
2
/*
3
*
4
* * Copyright (c) 2022-2025 Bearsampp
5
* * License: GNU General Public License version 3 or later; see LICENSE.txt
6
* * Website: https://bearsampp.com
7
* * Github: https://github.com/Bearsampp
8
*
9
*/
10
29
class
Log
30
{
31
const
ERROR
=
'ERROR'
;
32
const
WARNING
=
'WARNING'
;
33
const
INFO
=
'INFO'
;
34
const
DEBUG
=
'DEBUG'
;
35
const
TRACE
=
'TRACE'
;
36
38
private
static
$logBuffer
= [];
39
41
private
static
$logBufferSize
= 50;
42
44
private
static
$shutdownRegistered
=
false
;
45
47
private
static
$logStats
= [
48
'buffered'
=> 0,
49
'flushed'
=> 0,
50
'writes'
=> 0,
51
'async'
=> 0,
52
];
53
55
private
static
$asyncQueueDir
=
null
;
56
58
private
static
$asyncEnabled
=
true
;
59
61
private
static
$maxAsyncQueueBatch
= 5;
62
72
public
static
function
init
()
73
{
74
if
(!self::$shutdownRegistered) {
75
register_shutdown_function([__CLASS__,
'flush'
]);
76
register_shutdown_function([__CLASS__,
'processAsyncQueue'
]);
77
self::$shutdownRegistered =
true
;
78
79
// Initialize async queue directory
80
self::initializeAsyncQueue
();
81
82
// Auto-disable async when TRACE logging is enabled (for live log monitoring)
83
self::checkVerbosityAndAdjustAsync
();
84
}
85
}
86
94
private
static
function
checkVerbosityAndAdjustAsync
()
95
{
96
global
$bearsamppConfig
;
97
98
try
{
99
if
(!isset(
$bearsamppConfig
)) {
100
return
;
101
}
102
103
$verbosity =
$bearsamppConfig
->getLogsVerbose();
104
105
// Disable async and reduce buffer size for DEBUG/TRACE logging
106
// This ensures live log monitoring shows logs immediately
107
if
($verbosity ===
Config::VERBOSE_TRACE
) {
108
self::$asyncEnabled =
false
;
109
// Use smaller buffer for TRACE to flush more frequently (every 10 entries)
110
self::$logBufferSize = 10;
111
} elseif ($verbosity ===
Config::VERBOSE_DEBUG
) {
112
self::$asyncEnabled =
false
;
113
// For DEBUG level, use moderate buffer size (25 entries)
114
self::$logBufferSize = 25;
115
}
116
// For INFO and REPORT levels, use default buffer size (50) with async enabled
117
}
catch
(Exception $e) {
118
// Silently fail - this is just an optimization
119
}
120
}
121
127
private
static
function
initializeAsyncQueue
()
128
{
129
try
{
130
// Set up queue directory in tmp
131
self::$asyncQueueDir =
Path::getTmpPath
() .
'/log-queue'
;
132
133
// Create queue directory if it doesn't exist
134
if
(!is_dir(self::$asyncQueueDir)) {
135
@mkdir(self::$asyncQueueDir, 0755,
true
);
136
}
137
}
catch
(Exception $e) {
138
self::$asyncEnabled =
false
;
139
}
140
}
141
154
private
static
function
write
($data, $type, $file =
null
)
155
{
156
global
$bearsamppRoot
,
$bearsamppCore
,
$bearsamppConfig
;
157
158
// Safety check: if globals aren't initialised, fall back to error_log
159
if
(!isset(
$bearsamppRoot
) || !isset(
$bearsamppCore
) || !isset(
$bearsamppConfig
)) {
160
error_log(
'['
. $type .
'] '
. $data);
161
return
;
162
}
163
164
// Lazily register the shutdown handler if init() was not called explicitly
165
self::init
();
166
167
// Resolve default file path only when the caller did not supply one
168
if
($file ===
null
) {
169
$file = $type === self::ERROR
170
?
Path::getErrorLogFilePath
()
171
:
Path::getLogFilePath
();
172
173
if
(!
$bearsamppRoot
->isRoot()) {
174
$file =
Path::getHomepageLogFilePath
();
175
}
176
}
177
178
$verbose = [];
179
$verbose[
Config::VERBOSE_SIMPLE
] = $type === self::ERROR || $type === self::WARNING;
180
$verbose[
Config::VERBOSE_REPORT
] = $verbose[
Config::VERBOSE_SIMPLE
] || $type === self::INFO;
181
$verbose[
Config::VERBOSE_DEBUG
] = $verbose[
Config::VERBOSE_REPORT
] || $type === self::DEBUG;
182
$verbose[
Config::VERBOSE_TRACE
] = $verbose[
Config::VERBOSE_DEBUG
] || $type === self::TRACE;
183
184
$writeLog =
false
;
185
if
(
$bearsamppConfig
->getLogsVerbose() ===
Config::VERBOSE_SIMPLE
&& $verbose[
Config::VERBOSE_SIMPLE
]) {
186
$writeLog =
true
;
187
} elseif (
$bearsamppConfig
->getLogsVerbose() ===
Config::VERBOSE_REPORT
&& $verbose[
Config::VERBOSE_REPORT
]) {
188
$writeLog =
true
;
189
} elseif (
$bearsamppConfig
->getLogsVerbose() ===
Config::VERBOSE_DEBUG
&& $verbose[
Config::VERBOSE_DEBUG
]) {
190
$writeLog =
true
;
191
} elseif (
$bearsamppConfig
->getLogsVerbose() ===
Config::VERBOSE_TRACE
&& $verbose[
Config::VERBOSE_TRACE
]) {
192
$writeLog =
true
;
193
}
194
195
if
($writeLog) {
196
self::$logBuffer[] = [
197
'file'
=> $file,
198
'data'
=> $data,
199
'type'
=> $type,
200
'time'
=> time(),
201
];
202
self::$logStats[
'buffered'
]++;
203
204
// Flush immediately for:
205
// 1. Errors (always)
206
// 2. TRACE/DEBUG level logs (for real-time visibility during debugging)
207
// 3. When buffer reaches the configured size limit
208
$debugVerbosity =
$bearsamppConfig
->getLogsVerbose();
209
$isDebugMode = ($debugVerbosity ===
Config::VERBOSE_TRACE
|| $debugVerbosity ===
Config::VERBOSE_DEBUG
);
210
$shouldFlush = $type === self::ERROR ||
211
$isDebugMode ||
212
count(self::$logBuffer) >= self::$logBufferSize;
213
214
if
($shouldFlush) {
215
self::flush
();
216
}
217
}
218
}
219
228
public
static
function
flush
()
229
{
230
if
(empty(self::$logBuffer)) {
231
return
;
232
}
233
234
global
$bearsamppCore
,
$bearsamppConfig
;
235
236
// If the core global is gone (e.g. during an abnormal shutdown), fall back to error_log
237
if
(!isset(
$bearsamppCore
)) {
238
foreach
(self::$logBuffer as $log) {
239
error_log(
'['
. date(
'Y-m-d H:i:s'
, $log[
'time'
]) .
'] ['
. $log[
'type'
] .
'] '
. $log[
'data'
]);
240
}
241
self::$logStats[
'flushed'
] += count(self::$logBuffer);
242
self::$logBuffer = [];
243
return
;
244
}
245
246
// Check if DEBUG or TRACE logging is active and force sync writes
247
$forceSync =
false
;
248
try
{
249
if
(isset(
$bearsamppConfig
)) {
250
$verbosity =
$bearsamppConfig
->getLogsVerbose();
251
if
($verbosity ===
Config::VERBOSE_TRACE
|| $verbosity ===
Config::VERBOSE_DEBUG
) {
252
$forceSync =
true
;
253
}
254
}
255
}
catch
(Exception $e) {
256
// Ignore errors checking config
257
}
258
259
// Group logs by destination file
260
$logsByFile = [];
261
foreach
(self::$logBuffer as $log) {
262
$logsByFile[$log[
'file'
]][] = $log;
263
}
264
265
foreach
($logsByFile as $file => $logs) {
266
$content =
''
;
267
foreach
($logs as $log) {
268
$content .=
'['
. date(
'Y-m-d H:i:s'
, $log[
'time'
]) .
'] # '
.
269
APP_TITLE
.
' '
.
$bearsamppCore
->getAppVersion() .
' # '
.
270
$log[
'type'
] .
': '
. $log[
'data'
] . PHP_EOL;
271
}
272
273
// Use sync writes if TRACE is enabled or async is disabled
274
if
(!$forceSync && self::$asyncEnabled) {
275
// Queue for async processing (non-blocking)
276
$queued =
self::queueAsyncWrite
($file, $content);
277
278
if
($queued) {
279
// Successfully queued, content will be written in background
280
self::$logStats[
'async'
]++;
281
self::$logStats[
'writes'
]++;
282
continue
;
283
}
284
// If async queueing failed, fall through to sync write
285
}
286
287
// Synchronous write (immediate for TRACE, fallback for others)
288
$written = @file_put_contents($file, $content, FILE_APPEND | LOCK_EX);
289
if
($written ===
false
) {
290
// File write failed — ensure entries are not silently lost
291
foreach
($logs as $log) {
292
error_log(
'['
. $log[
'type'
] .
'] '
. $log[
'data'
] .
' (target: '
. $file .
')'
);
293
}
294
}
295
296
self::$logStats[
'writes'
]++;
297
}
298
299
self::$logStats[
'flushed'
] += count(self::$logBuffer);
300
self::$logBuffer = [];
301
}
302
311
private
static
function
queueAsyncWrite
($file, $content)
312
{
313
if
(!self::$asyncEnabled || !is_dir(self::$asyncQueueDir)) {
314
return
false
;
315
}
316
317
try
{
318
// Create a unique queue file for this write
319
$queueFile = self::$asyncQueueDir .
'/'
. uniqid(
'log_'
,
true
) .
'.queue'
;
320
321
// Queue entry contains the target file and content
322
$queueEntry = [
323
'file'
=> $file,
324
'content'
=> $content,
325
'timestamp'
=> time(),
326
];
327
328
// Write queue entry (this is a small, fast operation)
329
$serialized = serialize($queueEntry);
330
$written = @file_put_contents($queueFile, $serialized, LOCK_EX);
331
332
return
($written !==
false
);
333
}
catch
(Exception $e) {
334
// Silently fail - this is async so we don't want to interrupt main process
335
return
false
;
336
}
337
}
338
347
public
static
function
flushAsyncQueue
()
348
{
349
return
self::processAsyncQueue
();
350
}
351
358
public
static
function
processAsyncQueue
()
359
{
360
if
(!is_dir(self::$asyncQueueDir)) {
361
return
0;
362
}
363
364
$processed = 0;
365
366
try
{
367
$queueFiles = glob(self::$asyncQueueDir .
'/*.queue'
);
368
369
if
(empty($queueFiles)) {
370
return
0;
371
}
372
373
// Group entries by target file
374
$entriesByFile = [];
375
376
foreach
($queueFiles as $queueFile) {
377
try
{
378
// Read and deserialize queue entry
379
$serialized = @file_get_contents($queueFile);
380
if
($serialized ===
false
) {
381
continue
;
382
}
383
384
$entry = @unserialize($serialized, [
'allowed_classes'
=>
false
]);
385
if
($entry ===
false
|| !isset($entry[
'file'
]) || !isset($entry[
'content'
])) {
386
// Invalid queue entry, remove it
387
@unlink($queueFile);
388
continue
;
389
}
390
391
// Group by target file
392
$targetFile = $entry[
'file'
];
393
if
(!isset($entriesByFile[$targetFile])) {
394
$entriesByFile[$targetFile] = [];
395
}
396
397
$entriesByFile[$targetFile][] = $entry[
'content'
];
398
$processed++;
399
400
// Remove the processed queue file
401
@unlink($queueFile);
402
403
}
catch
(Exception $e) {
404
// Skip bad entries
405
@unlink($queueFile);
406
}
407
}
408
409
// Write all accumulated entries to their target files
410
foreach
($entriesByFile as $targetFile => $contents) {
411
try
{
412
$combined = implode(
''
, $contents);
413
@file_put_contents($targetFile, $combined, FILE_APPEND | LOCK_EX);
414
}
catch
(Exception $e) {
415
// Log write failed - use error_log as fallback
416
error_log(
'Failed to write to '
. $targetFile);
417
}
418
}
419
420
// Clean up any stale queue files
421
self::cleanupStaleAsyncQueue
(3600);
422
423
}
catch
(Exception $e) {
424
// Silently fail
425
}
426
427
return
$processed;
428
}
429
437
private
static
function
cleanupStaleAsyncQueue
($maxAge)
438
{
439
if
(!is_dir(self::$asyncQueueDir)) {
440
return
0;
441
}
442
443
$removed = 0;
444
$now = time();
445
446
try
{
447
$queueFiles = @glob(self::$asyncQueueDir .
'/*.queue'
);
448
449
if
(!is_array($queueFiles)) {
450
return
0;
451
}
452
453
foreach
($queueFiles as $queueFile) {
454
try
{
455
// Remove files older than maxAge
456
if
($now - @filemtime($queueFile) > $maxAge) {
457
@unlink($queueFile);
458
$removed++;
459
}
460
}
catch
(Exception $e) {
461
// Skip
462
}
463
}
464
}
catch
(Exception $e) {
465
// Ignore cleanup errors
466
}
467
468
return
$removed;
469
}
470
477
public
static
function
reset
()
478
{
479
self::$logBuffer = [];
480
self::$shutdownRegistered =
false
;
481
self::$logStats = [
482
'buffered'
=> 0,
483
'flushed'
=> 0,
484
'writes'
=> 0,
485
'async'
=> 0,
486
];
487
}
488
495
public
static
function
setAsyncEnabled
($enabled)
496
{
497
self::$asyncEnabled = (bool)$enabled;
498
}
499
505
public
static
function
isAsyncEnabled
()
506
{
507
return
self::$asyncEnabled;
508
}
509
515
public
static
function
getAsyncQueueDir
()
516
{
517
return
self::$asyncQueueDir;
518
}
519
525
public
static
function
getAsyncQueueSize
()
526
{
527
if
(!is_dir(self::$asyncQueueDir)) {
528
return
0;
529
}
530
531
$files = @glob(self::$asyncQueueDir .
'/*.queue'
);
532
return
is_array($files) ? count($files) : 0;
533
}
534
540
public
static
function
getStats
()
541
{
542
return
self::$logStats;
543
}
544
551
public
static
function
setBufferSize
($size)
552
{
553
if
($size > 0 && $size <= 1000) {
554
self::$logBufferSize = $size;
555
}
556
}
557
563
public
static
function
getBufferSize
()
564
{
565
return
self::$logBufferSize;
566
}
567
573
public
static
function
separator
()
574
{
575
global
$bearsamppRoot
;
576
577
$logs = [
578
Path::getLogFilePath
(),
579
Path::getErrorLogFilePath
(),
580
Path::getServicesLogFilePath
(),
581
Path::getRegistryLogFilePath
(),
582
Path::getStartupLogFilePath
(),
583
Path::getBatchLogFilePath
(),
584
Path::getWinbinderLogFilePath
(),
585
];
586
587
$separator =
'========================================================================================'
. PHP_EOL;
588
foreach
($logs as $log) {
589
if
(!file_exists($log)) {
590
continue
;
591
}
592
$logContent = @file_get_contents($log);
593
if
($logContent !==
false
&& !str_ends_with($logContent, $separator)) {
594
file_put_contents($log, $separator, FILE_APPEND);
595
}
596
}
597
}
598
605
public
static
function
trace
($data, $file =
null
)
606
{
607
self::write
($data, self::TRACE, $file);
608
}
609
616
public
static
function
debug
($data, $file =
null
)
617
{
618
self::write
($data, self::DEBUG, $file);
619
}
620
627
public
static
function
info
($data, $file =
null
)
628
{
629
self::write
($data, self::INFO, $file);
630
}
631
638
public
static
function
warning
($data, $file =
null
)
639
{
640
self::write
($data, self::WARNING, $file);
641
}
642
650
public
static
function
error
($data, $file =
null
)
651
{
652
self::write
($data, self::ERROR, $file);
653
}
654
660
public
static
function
initClass
($classInstance)
661
{
662
self::trace
(
'Init '
. get_class($classInstance));
663
}
664
670
public
static
function
reloadClass
($classInstance)
671
{
672
self::trace
(
'Reload '
. get_class($classInstance));
673
}
674
}
675
$bearsamppRoot
global $bearsamppRoot
Definition
ajax.apache.php:16
$bearsamppCore
global $bearsamppCore
Definition
ajax.latestversion.php:24
Config\VERBOSE_REPORT
const VERBOSE_REPORT
Definition
class.config.php:39
Config\VERBOSE_SIMPLE
const VERBOSE_SIMPLE
Definition
class.config.php:38
Config\VERBOSE_TRACE
const VERBOSE_TRACE
Definition
class.config.php:41
Config\VERBOSE_DEBUG
const VERBOSE_DEBUG
Definition
class.config.php:40
Log
Definition
class.log.php:30
Log\queueAsyncWrite
static queueAsyncWrite($file, $content)
Definition
class.log.php:311
Log\flush
static flush()
Definition
class.log.php:228
Log\setBufferSize
static setBufferSize($size)
Definition
class.log.php:551
Log\$logStats
static $logStats
Definition
class.log.php:47
Log\info
static info($data, $file=null)
Definition
class.log.php:627
Log\getBufferSize
static getBufferSize()
Definition
class.log.php:563
Log\reset
static reset()
Definition
class.log.php:477
Log\getAsyncQueueSize
static getAsyncQueueSize()
Definition
class.log.php:525
Log\$asyncQueueDir
static $asyncQueueDir
Definition
class.log.php:55
Log\debug
static debug($data, $file=null)
Definition
class.log.php:616
Log\initializeAsyncQueue
static initializeAsyncQueue()
Definition
class.log.php:127
Log\$asyncEnabled
static $asyncEnabled
Definition
class.log.php:58
Log\setAsyncEnabled
static setAsyncEnabled($enabled)
Definition
class.log.php:495
Log\getStats
static getStats()
Definition
class.log.php:540
Log\$logBuffer
static $logBuffer
Definition
class.log.php:38
Log\DEBUG
const DEBUG
Definition
class.log.php:34
Log\ERROR
const ERROR
Definition
class.log.php:31
Log\warning
static warning($data, $file=null)
Definition
class.log.php:638
Log\$logBufferSize
static $logBufferSize
Definition
class.log.php:41
Log\reloadClass
static reloadClass($classInstance)
Definition
class.log.php:670
Log\processAsyncQueue
static processAsyncQueue()
Definition
class.log.php:358
Log\write
static write($data, $type, $file=null)
Definition
class.log.php:154
Log\$maxAsyncQueueBatch
static $maxAsyncQueueBatch
Definition
class.log.php:61
Log\init
static init()
Definition
class.log.php:72
Log\$shutdownRegistered
static $shutdownRegistered
Definition
class.log.php:44
Log\trace
static trace($data, $file=null)
Definition
class.log.php:605
Log\checkVerbosityAndAdjustAsync
static checkVerbosityAndAdjustAsync()
Definition
class.log.php:94
Log\error
static error($data, $file=null)
Definition
class.log.php:650
Log\getAsyncQueueDir
static getAsyncQueueDir()
Definition
class.log.php:515
Log\isAsyncEnabled
static isAsyncEnabled()
Definition
class.log.php:505
Log\WARNING
const WARNING
Definition
class.log.php:32
Log\flushAsyncQueue
static flushAsyncQueue()
Definition
class.log.php:347
Log\TRACE
const TRACE
Definition
class.log.php:35
Log\separator
static separator()
Definition
class.log.php:573
Log\INFO
const INFO
Definition
class.log.php:33
Log\initClass
static initClass($classInstance)
Definition
class.log.php:660
Log\cleanupStaleAsyncQueue
static cleanupStaleAsyncQueue($maxAge)
Definition
class.log.php:437
Path\getBatchLogFilePath
static getBatchLogFilePath($aetrayPath=false)
Definition
class.path.php:353
Path\getLogFilePath
static getLogFilePath($aetrayPath=false)
Definition
class.path.php:615
Path\getStartupLogFilePath
static getStartupLogFilePath($aetrayPath=false)
Definition
class.path.php:929
Path\getServicesLogFilePath
static getServicesLogFilePath($aetrayPath=false)
Definition
class.path.php:874
Path\getRegistryLogFilePath
static getRegistryLogFilePath($aetrayPath=false)
Definition
class.path.php:819
Path\getTmpPath
static getTmpPath($aetrayPath=false)
Definition
class.path.php:940
Path\getHomepageLogFilePath
static getHomepageLogFilePath($aetrayPath=false)
Definition
class.path.php:472
Path\getErrorLogFilePath
static getErrorLogFilePath($aetrayPath=false)
Definition
class.path.php:439
Path\getWinbinderLogFilePath
static getWinbinderLogFilePath($aetrayPath=false)
Definition
class.path.php:1067
$bearsamppConfig
global $bearsamppConfig
Definition
homepage.php:41
APP_TITLE
const APP_TITLE
Definition
root.php:13
sandbox
core
classes
class.log.php
Generated by
1.17.0