Bearsampp 2026.9.23
Loading...
Searching...
No Matches
Log Class Reference

Static Public Member Functions

static commitSilentBuffer ()
static debug ($data, $file=null)
static error ($data, $file=null)
static flush ()
static flushAsyncQueue ()
static getAsyncQueueDir ()
static getAsyncQueueSize ()
static getBufferSize ()
static getStats ()
static info ($data, $file=null)
static init ()
static initClass ($classInstance)
static isAsyncEnabled ()
static isSilent ()
static processAsyncQueue ()
static reloadClass ($classInstance)
static reset ()
static rollbackSilentBuffer ()
static separator ()
static setAsyncEnabled ($enabled)
static setBufferSize ($size)
static startSilentBuffer ()
static trace ($data, $file=null)
static warning ($data, $file=null)

Data Fields

const DEBUG = 'DEBUG'
const ERROR = 'ERROR'
const INFO = 'INFO'
const MAX_QUEUE_BATCH = 200
const MAX_QUEUE_CONTENT_LENGTH = 1048576
const MAX_QUEUE_TARGETS = 50
const TRACE = 'TRACE'
const WARNING = 'WARNING'

Static Private Member Functions

static checkVerbosityAndAdjustAsync ()
static cleanupStaleAsyncQueue ($maxAge)
static initializeAsyncQueue ()
static queueAsyncWrite ($file, $content)
static sanitizeQueueTarget ($file)
static write ($data, $type, $file=null)

Static Private Attributes

static $asyncEnabled = true
static $asyncQueueDir = null
static $initClassCounts = array()
static $logBuffer = []
static $logBufferSize = 50
static $logStats
static $maxAsyncQueueBatch = 5
static $reloadClassCounts = array()
static $shutdownRegistered = false
static $silentMode = false

Detailed Description

Log class providing static logging methods with buffered and asynchronous I/O.

Supports five log levels (TRACE, DEBUG, INFO, WARNING, ERROR), a separator utility, class-lifecycle helpers, and a configurable write buffer to reduce file-system pressure. Also supports asynchronous logging to decouple I/O operations from the main process.

Call Log\init() once during bootstrap (after globals are available) to register the shutdown flush handler and initialize async logging.

Usage:

Log::info('Application started');
Log::error('Something went wrong');
static info($data, $file=null)
static init()
Definition class.log.php:84
static error($data, $file=null)

Definition at line 29 of file class.log.php.

Member Function Documentation

◆ checkVerbosityAndAdjustAsync()

checkVerbosityAndAdjustAsync ( )
staticprivate

Check logging verbosity and adjust async settings accordingly. Disables async when TRACE level is enabled for real-time log visibility. Also optimizes buffer size for better responsiveness during debugging.

Returns
void

Definition at line 106 of file class.log.php.

107 {
108 global $bearsamppConfig;
109
110 try {
111 if (!isset($bearsamppConfig)) {
112 return;
113 }
114
115 $verbosity = $bearsamppConfig->getLogsVerbose();
116
117 // Disable async and reduce buffer size for DEBUG/TRACE logging
118 // This ensures live log monitoring shows logs immediately
119 if ($verbosity === Config::VERBOSE_TRACE) {
120 self::$asyncEnabled = false;
121 // Use smaller buffer for TRACE to flush more frequently (every 10 entries)
122 self::$logBufferSize = 10;
123 } elseif ($verbosity === Config::VERBOSE_DEBUG) {
124 self::$asyncEnabled = false;
125 // For DEBUG level, use moderate buffer size (25 entries)
126 self::$logBufferSize = 25;
127 }
128 // For INFO and REPORT levels, use default buffer size (50) with async enabled
129 } catch (Exception $e) {
130 // Silently fail - this is just an optimization
131 }
132 }
const VERBOSE_TRACE
const VERBOSE_DEBUG
global $bearsamppConfig
Definition homepage.php:46

References $bearsamppConfig, Config\VERBOSE_DEBUG, and Config\VERBOSE_TRACE.

Referenced by init().

Here is the caller graph for this function:

◆ cleanupStaleAsyncQueue()

cleanupStaleAsyncQueue ( $maxAge)
staticprivate

Clean up stale queue files that weren't processed. Prevents queue directory from filling up with old entries.

Parameters
int$maxAgeMaximum age in seconds
Returns
int Number of files removed

Definition at line 539 of file class.log.php.

540 {
541 if (!is_dir(self::$asyncQueueDir)) {
542 return 0;
543 }
544
545 $removed = 0;
546 $now = time();
547
548 try {
549 $queueFiles = @glob(self::$asyncQueueDir . '/*.queue');
550
551 if (!is_array($queueFiles)) {
552 return 0;
553 }
554
555 foreach ($queueFiles as $queueFile) {
556 try {
557 // Remove files older than maxAge
558 if ($now - @filemtime($queueFile) > $maxAge) {
559 @unlink($queueFile);
560 $removed++;
561 }
562 } catch (Exception $e) {
563 // Skip
564 }
565 }
566 } catch (Exception $e) {
567 // Ignore cleanup errors
568 }
569
570 return $removed;
571 }

Referenced by processAsyncQueue().

Here is the caller graph for this function:

◆ commitSilentBuffer()

commitSilentBuffer ( )
static

Exits silent mode and flushes all buffered entries to disk.

Returns
void

Definition at line 841 of file class.log.php.

842 {
843 self::$silentMode = false;
844 self::flush();
845 }
static flush()

References flush().

Here is the call graph for this function:

◆ debug()

debug ( $data,
$file = null )
static

Logs a DEBUG-level message.

Parameters
mixed$data
string | null$file

Definition at line 726 of file class.log.php.

727 {
728 self::write($data, self::DEBUG, $file);
729 }
static write($data, $type, $file=null)

References write().

Referenced by ActionCheckVersion\__construct(), ActionQuit\__construct(), Action\call(), Path\changePath(), BinApache\changePort(), BinMailpit\changePort(), BinMariadb\changePort(), BinMemcached\changePort(), BinMysql\changePort(), BinPostgresql\changePort(), BinXlight\changePort(), QuickPick\checkDownloadId(), ActionQuit\checkForOrphanedProcesses(), BinApache\checkPort(), BinMailpit\checkPort(), BinMariadb\checkPort(), BinMemcached\checkPort(), BinMysql\checkPort(), BinPostgresql\checkPort(), BinXlight\checkPort(), Csrf\cleanupExpiredTokens(), ActionQuit\cleanupTemporaryFiles(), Win32Native\countFilesFolders(), Win32Native\countFilesFoldersCOM(), Win32Native\createShortcut(), QuickPick\fetchAndUnzipModule(), Csrf\generateToken(), Registry\getAppBinsRegKey(), Win32Native\getDefaultBrowser(), Util\getFilesToScan(), Win32Native\getInstalledBrowsers(), QuickPick\getModuleUrl(), Win32Native\getProcessList(), Win32Native\getServiceInfo(), Win32Native\getSpecialFolderPath(), QuickPick\getVersions(), BinPostgresql\handleNonPostgresUsage(), Win32Service\infos(), QuickPick\installModule(), Win32Native\killProcess(), Win32Native\listServices(), ModuleLoader\loadAsync(), ModuleLoader\loadSync(), QuickPick\logHeaders(), ActionQuit\performQuickCleanupVerification(), Action\process(), ActionQuit\processWindow(), QuickPick\rebuildQuickpickJson(), BinApache\refreshAlias(), BinApache\refreshConf(), BinApache\refreshVhosts(), QuickPick\regenerateMenuSafe(), Win32Native\registryDeleteKey(), Win32Native\registryDeleteValue(), Win32Native\registryExists(), Win32Native\registryGetValue(), Win32Native\registrySetValue(), Symlinks\safeRemoveSymlink(), ActionStartup\scanFolders(), Cache\set(), Cache\setDuration(), BinApache\setEnable(), BinMailpit\setEnable(), BinMariadb\setEnable(), BinMemcached\setEnable(), BinMysql\setEnable(), BinNodejs\setEnable(), BinPhp\setEnable(), BinPostgresql\setEnable(), BinXlight\setEnable(), ActionCheckVersion\showVersionOkMessageBox(), Win32Service\start(), BinApache\switchVersion(), BinMailpit\switchVersion(), BinMariadb\switchVersion(), BinMemcached\switchVersion(), BinMysql\switchVersion(), BinNodejs\switchVersion(), BinPhp\switchVersion(), BinPostgresql\switchVersion(), BinXlight\switchVersion(), Core\unzipFile(), AppPhpmyadmin\updateConfig(), AppPhppgadmin\updateConfig(), BinApache\updateConfig(), BinMailpit\updateConfig(), BinMariadb\updateConfig(), BinMemcached\updateConfig(), BinMysql\updateConfig(), BinNodejs\updateConfig(), BinPhp\updateConfig(), BinPostgresql\updateConfig(), BinXlight\updateConfig(), Module\updateConfig(), ToolGit\updateConfig(), QuickPick\updateModuleConfig(), Csrf\validateToken(), QuickPick\verifyModuleChecksum(), ActionQuit\verifyServicesStoppedAndCleanup(), ActionStartup\writeLog(), Batch\writeLog(), CommandRunner\writeLog(), Nssm\writeLog(), Registry\writeLog(), Win32Service\writeLog(), and WinBinder\writeLog().

Here is the call graph for this function:

◆ error()

error ( $data,
$file = null )
static

Logs an ERROR-level message. Errors bypass the buffer and are written immediately.

Parameters
mixed$data
string | null$file

Definition at line 760 of file class.log.php.

761 {
762 self::write($data, self::ERROR, $file);
763 }

References write().

Referenced by ActionCheckVersion\__construct(), ActionLoading\__construct(), ActionReload\__construct(), FiberModule\__construct(), Path\changePath(), BinApache\changePort(), BinMailpit\changePort(), BinMariadb\changePort(), BinMemcached\changePort(), BinMysql\changePort(), BinPostgresql\changePort(), BinXlight\changePort(), QuickPick\checkDownloadId(), ActionQuit\checkForOrphanedProcesses(), BinApache\checkPort(), BinMailpit\checkPort(), BinMariadb\checkPort(), BinMemcached\checkPort(), BinMysql\checkPort(), BinPostgresql\checkPort(), BinXlight\checkPort(), ActionQuit\cleanupTemporaryFiles(), Win32Native\countFilesFolders(), Win32Native\countFilesFoldersCOM(), OpenSsl\createCrt(), Symlinks\createModuleSymlink(), Win32Native\createShortcut(), ActionStartup\createSslCrts(), Symlinks\deleteCurrentSymlinks(), Util\deleteFolder(), HttpClient\downloadFile(), OpenSsl\ensureMkcertExeExists(), OpenSsl\ensureRootCaExists(), OpenSsl\ensureSslDirExists(), QuickPick\fetchAndUnzipModule(), QuickPick\fetchChecksumFromSidecar(), HttpClient\fetchGet(), Win32Native\findProcessesByName(), UtilString\generateSecureBytes(), UtilString\generateSecureToken(), HttpClient\getApiJson(), Core\getAppVersion(), Cache\getCacheIntegrityKey(), Util\getLatestVersion(), QuickPick\getModuleUrl(), Win32Native\getProcessInfo(), Win32Native\getProcessList(), QuickPick\getQuickpickJson(), Win32Native\getServiceInfo(), Win32Native\getSpecialFolderPath(), QuickPick\getVersions(), Splash\init(), BinMariadb\initData(), QuickPick\installModule(), ServiceHelper\installService(), OpenSsl\isExpired(), Symlinks\isPathWithinAllowedBase(), Core\isSafeDestinationFileList(), Win32Native\killProcess(), Win32Native\listServices(), OpenSsl\makeRootCa(), ActionCheckVersion\processWindow(), HttpClient\proxyDownload(), HttpClient\proxyFetch(), UtilString\random(), OpenSsl\rebuildAllCerts(), Win32Native\registryDeleteKey(), Win32Native\registryDeleteValue(), Win32Native\registryExists(), Win32Native\registrySetValue(), AppPhpmyadmin\reload(), AppPhppgadmin\reload(), BinApache\reload(), BinMailpit\reload(), BinMariadb\reload(), BinMemcached\reload(), BinMysql\reload(), BinNodejs\reload(), BinPhp\reload(), BinPostgresql\reload(), BinXlight\reload(), ToolBruno\reload(), ToolComposer\reload(), ToolGhostscript\reload(), ToolGit\reload(), ToolNgrok\reload(), ToolPerl\reload(), ToolPowerShell\reload(), ToolPython\reload(), ToolRuby\reload(), OpenSsl\removeCrt(), ServiceHelper\removeService(), Symlinks\safeRemoveSymlink(), Win32Service\start(), ServiceHelper\startService(), Win32Service\stop(), TplAppReload\triggerReload(), OpenSsl\trustRootCaInFirefox(), Core\unzipFile(), AppPhpmyadmin\updateConfig(), AppPhppgadmin\updateConfig(), BinApache\updateConfig(), BinMailpit\updateConfig(), BinMariadb\updateConfig(), BinMemcached\updateConfig(), BinMysql\updateConfig(), BinNodejs\updateConfig(), BinPhp\updateConfig(), BinPostgresql\updateConfig(), BinXlight\updateConfig(), QuickPick\updateModuleConfig(), QuickPick\verifyModuleChecksum(), ActionQuit\verifyServicesStoppedAndCleanup(), ActionQuit\verifySymlinksRemoved(), FiberModule\wait(), ModuleLoader\waitForModule(), and Nssm\writeLogError().

Here is the call graph for this function:

◆ flush()

flush ( )
static

Flushes the log buffer to disk using async writes when available. Groups entries by file to minimise file operations. Uses async logging to avoid blocking the main process on I/O. Falls back to synchronous writes or error_log() if needed.

Returns
void

Definition at line 249 of file class.log.php.

250 {
251 if (empty(self::$logBuffer)) {
252 return;
253 }
254
256
257 // If the core global is gone (e.g. during an abnormal shutdown), fall back to error_log
258 if (!isset($bearsamppCore)) {
259 foreach (self::$logBuffer as $log) {
260 error_log('[' . date('Y-m-d H:i:s', $log['time']) . '] [' . $log['type'] . '] ' . $log['data']);
261 }
262 self::$logStats['flushed'] += count(self::$logBuffer);
263 self::$logBuffer = [];
264
265 return;
266 }
267
268 // Check if DEBUG or TRACE logging is active and force sync writes
269 $forceSync = false;
270 try {
271 if (isset($bearsamppConfig)) {
272 $verbosity = $bearsamppConfig->getLogsVerbose();
273 if ($verbosity === Config::VERBOSE_TRACE || $verbosity === Config::VERBOSE_DEBUG) {
274 $forceSync = true;
275 }
276 }
277 } catch (Exception $e) {
278 // Ignore errors checking config
279 }
280
281 // Group logs by destination file
282 $logsByFile = [];
283 foreach (self::$logBuffer as $log) {
284 $logsByFile[$log['file']][] = $log;
285 }
286
287 foreach ($logsByFile as $file => $logs) {
288 $content = '';
289 foreach ($logs as $log) {
290 $content .= '[' . date('Y-m-d H:i:s', $log['time']) . '] # ' .
291 APP_TITLE . ' ' . $bearsamppCore->getAppVersion() . ' # ' .
292 $log['type'] . ': ' . $log['data'] . PHP_EOL;
293 }
294
295 // Use sync writes if TRACE is enabled or async is disabled
296 if (!$forceSync && self::$asyncEnabled) {
297 // Queue for async processing (non-blocking)
298 $queued = self::queueAsyncWrite($file, $content);
299
300 if ($queued) {
301 // Successfully queued, content will be written in background
302 self::$logStats['async']++;
303 self::$logStats['writes']++;
304 continue;
305 }
306 // If async queueing failed, fall through to sync write
307 }
308
309 // Synchronous write (immediate for TRACE, fallback for others)
310 $written = @file_put_contents($file, $content, FILE_APPEND | LOCK_EX);
311 if ($written === false) {
312 // File write failed — ensure entries are not silently lost
313 foreach ($logs as $log) {
314 error_log('[' . $log['type'] . '] ' . $log['data'] . ' (target: ' . $file . ')');
315 }
316 }
317
318 self::$logStats['writes']++;
319 }
320
321 self::$logStats['flushed'] += count(self::$logBuffer);
322 self::$logBuffer = [];
323 }
global $bearsamppCore
static queueAsyncWrite($file, $content)
const APP_TITLE
Definition root.php:19

References $bearsamppConfig, $bearsamppCore, APP_TITLE, queueAsyncWrite(), Config\VERBOSE_DEBUG, and Config\VERBOSE_TRACE.

Referenced by commitSilentBuffer(), and write().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ flushAsyncQueue()

flushAsyncQueue ( )
static

Process all queued log entries immediately (public method for manual flushing). Useful for live log monitoring or ensuring logs are written before critical operations.

Can be called at any time to flush pending async log writes.

Returns
int Number of queue files processed

Definition at line 410 of file class.log.php.

411 {
412 $totalProcessed = 0;
413
414 // processAsyncQueue() processes one bounded batch; loop until nothing is left so the
415 // manual flush and the shutdown path drain the queue completely.
416 $iterationCap = 1000;
417 for ($i = 0; $i < $iterationCap; $i++) {
418 $processed = self::processAsyncQueue();
419 if ($processed <= 0) {
420 break;
421 }
422 $totalProcessed += $processed;
423 }
424
425 return $totalProcessed;
426 }
static processAsyncQueue()

References processAsyncQueue().

Here is the call graph for this function:

◆ getAsyncQueueDir()

getAsyncQueueDir ( )
static

Get the async queue directory path.

Returns
string|null

Definition at line 618 of file class.log.php.

619 {
620 return self::$asyncQueueDir;
621 }

◆ getAsyncQueueSize()

getAsyncQueueSize ( )
static

Get current async queue size.

Returns
int

Definition at line 628 of file class.log.php.

629 {
630 if (!is_dir(self::$asyncQueueDir)) {
631 return 0;
632 }
633
634 $files = @glob(self::$asyncQueueDir . '/*.queue');
635
636 return is_array($files) ? count($files) : 0;
637 }

◆ getBufferSize()

getBufferSize ( )
static

Returns the current log buffer size.

Returns
int

Definition at line 668 of file class.log.php.

669 {
670 return self::$logBufferSize;
671 }

◆ getStats()

getStats ( )
static

Returns the current log buffer statistics.

Returns
array Array with keys 'buffered', 'flushed', and 'writes'.

Definition at line 644 of file class.log.php.

645 {
646 return self::$logStats;
647 }

◆ info()

info ( $data,
$file = null )
static

Logs an INFO-level message.

Parameters
mixed$data
string | null$file

Definition at line 737 of file class.log.php.

738 {
739 self::write($data, self::INFO, $file);
740 }

References write().

Referenced by ActionQuit\__construct(), ActionReload\__construct(), ActionStartup\__construct(), ActionQuit\checkForOrphanedProcesses(), ActionQuit\cleanupTemporaryFiles(), Cache\clear(), ActionStartup\createSslCrts(), OpenSsl\ensureRootCaExists(), OpenSsl\ensureSslDirExists(), Batch\exitApp(), Util\getFilesToScan(), ServiceHelper\installService(), ActionStartup\installServicesSequential(), OpenSsl\makeRootCa(), OpenSsl\maybePromptTrustRootCa(), ActionQuit\performQuickCleanupVerification(), ActionQuit\processWindow(), OpenSsl\rebuildAllCerts(), AppPhpmyadmin\reload(), AppPhppgadmin\reload(), Apps\reload(), BinApache\reload(), BinMailpit\reload(), BinMariadb\reload(), BinMemcached\reload(), BinMysql\reload(), BinNodejs\reload(), BinPhp\reload(), BinPostgresql\reload(), Bins\reload(), BinXlight\reload(), ToolBruno\reload(), ToolComposer\reload(), ToolGhostscript\reload(), ToolGit\reload(), ToolNgrok\reload(), ToolPerl\reload(), ToolPowerShell\reload(), ToolPython\reload(), ToolRuby\reload(), OpenSsl\removeCrt(), ServiceHelper\removeService(), BinApache\setEnable(), BinMailpit\setEnable(), BinMariadb\setEnable(), BinMemcached\setEnable(), BinMysql\setEnable(), BinNodejs\setEnable(), BinPhp\setEnable(), BinPostgresql\setEnable(), BinXlight\setEnable(), Win32Service\start(), ServiceHelper\startAllServicesParallel(), ServiceHelper\stopAllServicesParallel(), OpenSsl\trustRootCaInFirefox(), Apps\update(), Bins\update(), Tools\update(), QuickPick\updateModuleConfig(), ActionQuit\verifyServicesStoppedAndCleanup(), ActionQuit\verifySymlinksRemoved(), and Nssm\writeLogInfo().

Here is the call graph for this function:

◆ init()

init ( )
static

Registers the shutdown flush handler and initializes async logging. Call once during bootstrap after globals are initialised.

Automatically disables async logging when TRACE verbosity is enabled, to ensure live log monitoring shows logs immediately.

Returns
void

Definition at line 84 of file class.log.php.

85 {
86 if (!self::$shutdownRegistered) {
87 register_shutdown_function([__CLASS__, 'flush']);
88 register_shutdown_function([__CLASS__, 'flushAsyncQueue']);
89 self::$shutdownRegistered = true;
90
91 // Initialize async queue directory
93
94 // Auto-disable async when TRACE logging is enabled (for live log monitoring)
96 }
97 }
static initializeAsyncQueue()
static checkVerbosityAndAdjustAsync()

References checkVerbosityAndAdjustAsync(), and initializeAsyncQueue().

Referenced by Root\register(), and write().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ initClass()

initClass ( $classInstance)
static

Logs the initialisation of a class instance at TRACE level. First occurrence of each class is logged at INFO level; subsequent occurrences are silently counted and a summary is emitted when the process terminates or on next unique init.

Parameters
object$classInstance

Definition at line 784 of file class.log.php.

785 {
786 $className = get_class($classInstance);
787
788 if (isset(self::$initClassCounts[$className])) {
789 self::$initClassCounts[$className]++;
790
791 return;
792 }
793
794 self::$initClassCounts[$className] = 1;
795 self::trace('Init ' . $className);
796 }
static trace($data, $file=null)

References trace().

Referenced by AppPhpmyadmin\__construct(), AppPhppgadmin\__construct(), BinApache\__construct(), BinMailpit\__construct(), BinMariadb\__construct(), BinMemcached\__construct(), BinMysql\__construct(), BinNodejs\__construct(), BinPhp\__construct(), BinPostgresql\__construct(), Bins\__construct(), BinXlight\__construct(), Homepage\__construct(), Nssm\__construct(), Registry\__construct(), Splash\__construct(), ToolBruno\__construct(), ToolComposer\__construct(), ToolGhostscript\__construct(), ToolGit\__construct(), ToolNgrok\__construct(), ToolPerl\__construct(), ToolPowerShell\__construct(), ToolPython\__construct(), ToolRuby\__construct(), Win32Service\__construct(), and WinBinder\__construct().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ initializeAsyncQueue()

initializeAsyncQueue ( )
staticprivate

Initialize async queue directory.

Returns
void

Definition at line 139 of file class.log.php.

140 {
141 try {
142 // Set up queue directory in tmp
143 self::$asyncQueueDir = Path::getTmpPath() . '/log-queue';
144
145 // Create queue directory if it doesn't exist
146 if (!is_dir(self::$asyncQueueDir)) {
147 @mkdir(self::$asyncQueueDir, 0755, true);
148 }
149 } catch (Exception $e) {
150 self::$asyncEnabled = false;
151 }
152 }
static getTmpPath($aetrayPath=false)

References Path\getTmpPath().

Referenced by init().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ isAsyncEnabled()

isAsyncEnabled ( )
static

Check if async logging is enabled.

Returns
bool

Definition at line 608 of file class.log.php.

609 {
610 return self::$asyncEnabled;
611 }

◆ isSilent()

isSilent ( )
static

Returns whether the logger is currently in silent buffer mode.

Returns
bool

Definition at line 863 of file class.log.php.

864 {
865 return self::$silentMode;
866 }

◆ processAsyncQueue()

processAsyncQueue ( )
static

Process all queued log entries at shutdown. Ensures any remaining queued logs are written before exit.

Returns
int Number of queue files processed

Definition at line 434 of file class.log.php.

435 {
436 if (!is_dir(self::$asyncQueueDir)) {
437 return 0;
438 }
439
440 $processed = 0;
441
442 try {
443 $queueFiles = glob(self::$asyncQueueDir . '/*.queue');
444 if (!is_array($queueFiles) || empty($queueFiles)) {
445 return 0;
446 }
447
448 // Bound the work performed in one pass: read at most MAX_QUEUE_BATCH files so
449 // a flood of queue entries cannot exhaust memory. Anything beyond the batch,
450 // or deferred by the target cap below, is left on disk for a later pass.
451 $batchFiles = array_slice($queueFiles, 0, self::MAX_QUEUE_BATCH);
452
453 // Group entries by target file
454 $entriesByFile = [];
455
456 foreach ($batchFiles as $queueFile) {
457 try {
458 // Read and deserialize queue entry
459 $serialized = @file_get_contents($queueFile);
460 if ($serialized === false) {
461 continue;
462 }
463
464 $entry = @unserialize($serialized, ['allowed_classes' => false]);
465 if ($entry === false || !isset($entry['file']) || !isset($entry['content'])) {
466 // Invalid queue entry, remove it
467 @unlink($queueFile);
468 continue;
469 }
470
471 // Validate the destination is inside the logs directory and not a path traversal.
472 // Queue files live in a shared tmp dir, so a local attacker could plant a rogue
473 // entry and redirect appends to arbitrary files if we trusted $entry['file'] blindly.
474 $targetFile = self::sanitizeQueueTarget($entry['file']);
475 if ($targetFile === null) {
476 // Invalid/unsafe target - discard the rogue entry
477 @unlink($queueFile);
478 continue;
479 }
480
481 // Cap the distinct target files processed in this pass. Once reached, stop
482 // accumulating and leave this entry (and those after it) untouched so they
483 // are preserved for a later pass rather than being dropped.
484 if (!isset($entriesByFile[$targetFile]) &&
485 count($entriesByFile) >= self::MAX_QUEUE_TARGETS) {
486 break;
487 }
488
489 // Cap the content size to prevent log bloat / disk exhaustion from rogue entries.
490 $content = (string)$entry['content'];
491 if (strlen($content) > self::MAX_QUEUE_CONTENT_LENGTH) {
492 @unlink($queueFile);
493 continue;
494 }
495
496 if (!isset($entriesByFile[$targetFile])) {
497 $entriesByFile[$targetFile] = [];
498 }
499
500 $entriesByFile[$targetFile][] = $content;
501 $processed++;
502
503 // Remove the processed queue file
504 @unlink($queueFile);
505 } catch (Exception $e) {
506 // Skip bad entries
507 @unlink($queueFile);
508 }
509 }
510
511 // Write all accumulated entries to their target files
512 foreach ($entriesByFile as $targetFile => $contents) {
513 try {
514 $combined = implode('', $contents);
515 @file_put_contents($targetFile, $combined, FILE_APPEND | LOCK_EX);
516 } catch (Exception $e) {
517 // Log write failed - use error_log as fallback
518 error_log('Failed to write to ' . $targetFile);
519 }
520 }
521
522 // Clean up any stale queue files
524 } catch (Exception $e) {
525 // Silently fail
526 }
527
528 return $processed;
529 }
static sanitizeQueueTarget($file)
static cleanupStaleAsyncQueue($maxAge)

References cleanupStaleAsyncQueue(), and sanitizeQueueTarget().

Referenced by flushAsyncQueue().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ queueAsyncWrite()

queueAsyncWrite ( $file,
$content )
staticprivate

Queue a log entry for asynchronous writing. Returns immediately without blocking on I/O.

Parameters
string$fileTarget log file path
string$contentLog content to write
Returns
bool True if queued successfully

Definition at line 334 of file class.log.php.

335 {
336 if (!self::$asyncEnabled || !is_dir(self::$asyncQueueDir)) {
337 return false;
338 }
339
340 // Defense-in-depth: reject oversized content at the source.
341 if (strlen((string)$content) > self::MAX_QUEUE_CONTENT_LENGTH) {
342 return false;
343 }
344
345 try {
346 // Create a unique queue file for this write
347 $queueFile = self::$asyncQueueDir . '/' . uniqid('log_', true) . '.queue';
348
349 // Queue entry contains the target file and content
350 $queueEntry = [
351 'file' => $file,
352 'content' => $content,
353 'timestamp' => time(),
354 ];
355
356 // Write queue entry (this is a small, fast operation)
357 $serialized = serialize($queueEntry);
358 $written = @file_put_contents($queueFile, $serialized, LOCK_EX);
359
360 return ($written !== false);
361 } catch (Exception $e) {
362 // Silently fail - this is async so we don't want to interrupt main process
363 return false;
364 }
365 }

Referenced by flush().

Here is the caller graph for this function:

◆ reloadClass()

reloadClass ( $classInstance)
static

Logs the reloading of a class instance at TRACE level. Only the first occurrence per class name is logged; subsequent duplicates are silently skipped.

Parameters
object$classInstance

Definition at line 805 of file class.log.php.

806 {
807 $className = get_class($classInstance);
808
809 if (isset(self::$reloadClassCounts[$className])) {
810 self::$reloadClassCounts[$className]++;
811
812 return;
813 }
814
815 self::$reloadClassCounts[$className] = 1;
816 self::trace('Reload ' . $className);
817 }

References trace().

Referenced by AppPhpmyadmin\reload(), AppPhppgadmin\reload(), BinApache\reload(), BinMailpit\reload(), BinMariadb\reload(), BinMemcached\reload(), BinMysql\reload(), BinNodejs\reload(), BinPhp\reload(), BinPostgresql\reload(), BinXlight\reload(), ToolBruno\reload(), ToolComposer\reload(), ToolGhostscript\reload(), ToolGit\reload(), ToolNgrok\reload(), ToolPerl\reload(), ToolPowerShell\reload(), ToolPython\reload(), and ToolRuby\reload().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ reset()

reset ( )
static

Clears the buffer and resets statistics. Useful in tests or when re-initialising the application.

Returns
void

Definition at line 579 of file class.log.php.

580 {
581 self::$logBuffer = [];
582 self::$shutdownRegistered = false;
583 self::$logStats = [
584 'buffered' => 0,
585 'flushed' => 0,
586 'writes' => 0,
587 'async' => 0,
588 ];
589 }

◆ rollbackSilentBuffer()

rollbackSilentBuffer ( )
static

Exits silent mode and discards all buffered entries without writing.

Returns
void

Definition at line 852 of file class.log.php.

853 {
854 self::$silentMode = false;
855 self::$logBuffer = [];
856 }

◆ sanitizeQueueTarget()

sanitizeQueueTarget ( $file)
staticprivate

Validates and normalizes a queue-entry target path.

Queue files live in a shared tmp directory, so a local attacker could plant a rogue entry whose 'file' value points anywhere on disk. To prevent arbitrary file appends, we only accept paths that resolve inside the Bearsampp logs directory and contain no path-traversal ('..') segments.

Parameters
string$fileRaw target path from a queue entry.
Returns
string|null Normalized safe path, or null if rejected.

Definition at line 379 of file class.log.php.

380 {
381 $file = (string)$file;
382 if ($file === '') {
383 return null;
384 }
385
386 $normalized = str_replace('\\', '/', $file);
387 if (strpos($normalized, '..') !== false) {
388 return null;
389 }
390
391 $logsPath = Path::getLogsPath();
392 $logsPathNormalized = str_replace('\\', '/', rtrim($logsPath, '/\\')) . '/';
393
394 // Only allow appends into the application logs directory.
395 if (strpos($normalized, $logsPathNormalized) !== 0) {
396 return null;
397 }
398
399 return $file;
400 }
static getLogsPath($aetrayPath=false)

References Path\getLogsPath().

Referenced by processAsyncQueue().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ separator()

separator ( )
static

Appends a separator line to each log file that does not already end with one.

@global object $bearsamppRoot

Definition at line 678 of file class.log.php.

679 {
680 if (self::$silentMode) {
681 return;
682 }
683
684 global $bearsamppRoot;
685
686 $logs = [
695 ];
696
697 $separator = '========================================================================================' . PHP_EOL;
698 foreach ($logs as $log) {
699 if (!file_exists($log)) {
700 continue;
701 }
702 $logContent = @file_get_contents($log);
703 if ($logContent !== false && !str_ends_with($logContent, $separator)) {
704 @file_put_contents($log, $separator, FILE_APPEND);
705 }
706 }
707 }
global $bearsamppRoot
static getBatchLogFilePath($aetrayPath=false)
static getLogFilePath($aetrayPath=false)
static getStartupLogFilePath($aetrayPath=false)
static getServicesLogFilePath($aetrayPath=false)
static getRegistryLogFilePath($aetrayPath=false)
static getHomepageLogFilePath($aetrayPath=false)
static getErrorLogFilePath($aetrayPath=false)
static getWinbinderLogFilePath($aetrayPath=false)

References $bearsamppRoot, Path\getBatchLogFilePath(), Path\getErrorLogFilePath(), Path\getHomepageLogFilePath(), Path\getLogFilePath(), Path\getRegistryLogFilePath(), Path\getServicesLogFilePath(), Path\getStartupLogFilePath(), and Path\getWinbinderLogFilePath().

Referenced by Root\register().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ setAsyncEnabled()

setAsyncEnabled ( $enabled)
static

Enable or disable async logging.

Parameters
bool$enabled
Returns
void

Definition at line 598 of file class.log.php.

599 {
600 self::$asyncEnabled = (bool)$enabled;
601 }

◆ setBufferSize()

setBufferSize ( $size)
static

Sets the log buffer size.

Parameters
int$sizeNew buffer size (1–1000).
Returns
void

Definition at line 656 of file class.log.php.

657 {
658 if ($size > 0 && $size <= 1000) {
659 self::$logBufferSize = $size;
660 }
661 }

◆ startSilentBuffer()

startSilentBuffer ( )
static

Enters silent buffer mode: all subsequent log entries are held in memory and not flushed to disk. ERROR-level entries always flush even in silent mode so critical failures are never lost.

Use commitSilentBuffer() to flush on success, or rollbackSilentBuffer() to discard the buffer.

Returns
void

Definition at line 829 of file class.log.php.

830 {
831 self::$silentMode = true;
832 // Discard anything already buffered from early bootstrap (e.g. separator)
833 self::$logBuffer = [];
834 }

◆ trace()

trace ( $data,
$file = null )
static

Logs a TRACE-level message.

Parameters
mixed$data
string | null$file

Definition at line 715 of file class.log.php.

716 {
717 self::write($data, self::TRACE, $file);
718 }

References write().

Referenced by ActionCheckVersion\__construct(), ActionExec\__construct(), ActionLoading\__construct(), ActionManualRestart\__construct(), ActionRebuildini\__construct(), Win32Service\callWin32Service(), BinMysql\changeRootPassword(), ActionLoading\checkAllServicesStarted(), ActionStartup\checkApacheServiceWithTimeout(), ActionStartup\checkMySQLServiceWithTimeout(), BinMysql\checkPort(), BinMysql\checkRootPassword(), Win32Service\create(), OpenSsl\createCrt(), ActionStartup\createSslCrts(), Win32Service\delete(), Symlinks\deleteCurrentSymlinks(), HttpClient\downloadFile(), Win32Service\ensureReset(), Win32Service\fastServiceCheck(), QuickPick\fetchChecksumFromSidecar(), ServiceHelper\forceKillService(), HttpClient\getApiJson(), HttpClient\getCurlHttpHeaders(), HttpClient\getHeaders(), HttpClient\getHttpHeaders(), Util\getLatestVersion(), Win32Service\getServices(), Win32Service\infos(), initClass(), BinMariadb\initData(), BinMysql\initData(), ActionStartup\installServices(), ActionStartup\installServicesSequential(), OpenSsl\isExpired(), Win32Service\isInstalled(), Win32Service\isPaused(), Win32Service\isPending(), Win32Service\isRunning(), Win32Service\isStopped(), ActionStartup\prepareService(), ActionLoading\processLoading(), ActionQuit\processWindow(), ActionStartup\processWindow(), ActionSwitchVersion\processWindow(), HttpClient\proxyDownload(), HttpClient\proxyFetch(), BinMailpit\rebuildConf(), BinApache\refreshAlias(), BinApache\refreshConf(), BinApache\refreshVhosts(), reloadClass(), Config\replaceAll(), Util\replaceInFile(), ActionStartup\rotationLogs(), Symlinks\safeRemoveSymlink(), ActionStartup\scanFolders(), BinMailpit\setVersion(), BinPhp\setVersion(), ServiceHelper\shutdownServicesParallel(), ServiceHelper\shutdownServicesSequential(), ServiceHelper\startAllServicesParallel(), Util\startLoading(), ServiceHelper\startServicesParallel(), ServiceHelper\startServicesSequential(), Win32Service\status(), Win32Service\stop(), ServiceHelper\stopAllServicesParallel(), ActionQuit\terminatePhpProcesses(), TplAppReload\triggerReload(), Core\unzipFile(), BinPhp\updateConfig(), ActionSwitchVersion\updateConfigVersion(), and Win32Service\waitForServiceDeletion().

Here is the call graph for this function:

◆ warning()

◆ write()

write ( $data,
$type,
$file = null )
staticprivate

Writes a message to a log file, using the buffer to reduce I/O.

The file path is resolved as follows:

  • If $file is provided by the caller it is used as-is.
  • Otherwise the default path is chosen based on $type (error log vs. main log), then overridden with the homepage log when not running in root context.
Parameters
mixed$dataThe message to log.
string$typeOne of the Log level constants.
string | null$fileExplicit file path, or null to use the default.

Definition at line 166 of file class.log.php.

167 {
169
170 // Safety check: if globals aren't initialised, fall back to error_log
171 if (!isset($bearsamppRoot) || !isset($bearsamppCore) || !isset($bearsamppConfig)) {
172 error_log('[' . $type . '] ' . $data);
173
174 return;
175 }
176
177 // Lazily register the shutdown handler if init() was not called explicitly
178 self::init();
179
180 // Resolve default file path only when the caller did not supply one
181 if ($file === null) {
182 $file = $type === self::ERROR
184 : Path::getLogFilePath();
185
186 if (!$bearsamppRoot->isRoot()) {
187 $file = Path::getHomepageLogFilePath();
188 }
189 }
190
191 $verbose = [];
192 $verbose[Config::VERBOSE_SIMPLE] = $type === self::ERROR || $type === self::WARNING;
196
197 $writeLog = false;
199 $writeLog = true;
200 } elseif ($bearsamppConfig->getLogsVerbose() === Config::VERBOSE_REPORT && $verbose[Config::VERBOSE_REPORT]) {
201 $writeLog = true;
202 } elseif ($bearsamppConfig->getLogsVerbose() === Config::VERBOSE_DEBUG && $verbose[Config::VERBOSE_DEBUG]) {
203 $writeLog = true;
204 } elseif ($bearsamppConfig->getLogsVerbose() === Config::VERBOSE_TRACE && $verbose[Config::VERBOSE_TRACE]) {
205 $writeLog = true;
206 }
207
208 if ($writeLog) {
209 // Sanitize line breaks to prevent log forging (injecting fake log lines).
210 self::$logBuffer[] = [
211 'file' => $file,
212 'data' => str_replace(["\r", "\n"], ' ', (string)$data),
213 'type' => $type,
214 'time' => time(),
215 ];
216 self::$logStats['buffered']++;
217
218 // In silent mode, suppress auto-flush so the caller can decide
219 // whether to commit (flush) or rollback (discard) the buffer.
220 // ERROR entries always flush — they must never be silently dropped.
221 if (self::$silentMode && $type !== self::ERROR) {
222 return;
223 }
224
225 // Flush immediately for:
226 // 1. Errors (always)
227 // 2. TRACE/DEBUG level logs (for real-time visibility during debugging)
228 // 3. When buffer reaches the configured size limit
229 $debugVerbosity = $bearsamppConfig->getLogsVerbose();
230 $isDebugMode = ($debugVerbosity === Config::VERBOSE_TRACE || $debugVerbosity === Config::VERBOSE_DEBUG);
231 $shouldFlush = $type === self::ERROR ||
232 $isDebugMode ||
233 count(self::$logBuffer) >= self::$logBufferSize;
234
235 if ($shouldFlush) {
236 self::flush();
237 }
238 }
239 }
const VERBOSE_REPORT
const VERBOSE_SIMPLE
$verbose
Definition pearcmd.php:167

References $bearsamppConfig, $bearsamppCore, $bearsamppRoot, $verbose, flush(), Path\getErrorLogFilePath(), Path\getHomepageLogFilePath(), Path\getLogFilePath(), init(), Config\VERBOSE_DEBUG, Config\VERBOSE_REPORT, Config\VERBOSE_SIMPLE, and Config\VERBOSE_TRACE.

Referenced by debug(), error(), info(), trace(), and warning().

Here is the call graph for this function:
Here is the caller graph for this function:

Field Documentation

◆ $asyncEnabled

$asyncEnabled = true
staticprivate

Definition at line 70 of file class.log.php.

◆ $asyncQueueDir

$asyncQueueDir = null
staticprivate

Definition at line 67 of file class.log.php.

◆ $initClassCounts

$initClassCounts = array()
staticprivate

Definition at line 771 of file class.log.php.

◆ $logBuffer

$logBuffer = []
staticprivate

Definition at line 47 of file class.log.php.

◆ $logBufferSize

$logBufferSize = 50
staticprivate

Definition at line 50 of file class.log.php.

◆ $logStats

$logStats
staticprivate
Initial value:
= [
'buffered' => 0,
'flushed' => 0,
'writes' => 0,
'async' => 0,
]

Definition at line 56 of file class.log.php.

◆ $maxAsyncQueueBatch

$maxAsyncQueueBatch = 5
staticprivate

Definition at line 73 of file class.log.php.

◆ $reloadClassCounts

$reloadClassCounts = array()
staticprivate

Definition at line 774 of file class.log.php.

◆ $shutdownRegistered

$shutdownRegistered = false
staticprivate

Definition at line 53 of file class.log.php.

◆ $silentMode

$silentMode = false
staticprivate

Definition at line 64 of file class.log.php.

◆ DEBUG

const DEBUG = 'DEBUG'

Definition at line 34 of file class.log.php.

◆ ERROR

const ERROR = 'ERROR'

Definition at line 31 of file class.log.php.

◆ INFO

const INFO = 'INFO'

Definition at line 33 of file class.log.php.

◆ MAX_QUEUE_BATCH

const MAX_QUEUE_BATCH = 200

Definition at line 41 of file class.log.php.

◆ MAX_QUEUE_CONTENT_LENGTH

const MAX_QUEUE_CONTENT_LENGTH = 1048576

Definition at line 38 of file class.log.php.

◆ MAX_QUEUE_TARGETS

const MAX_QUEUE_TARGETS = 50

Definition at line 44 of file class.log.php.

◆ TRACE

const TRACE = 'TRACE'

Definition at line 35 of file class.log.php.

◆ WARNING

const WARNING = 'WARNING'

Definition at line 32 of file class.log.php.


The documentation for this class was generated from the following file: