Bearsampp 2026.5.5
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
28class Log
29{
30 const ERROR = 'ERROR';
31 const WARNING = 'WARNING';
32 const INFO = 'INFO';
33 const DEBUG = 'DEBUG';
34 const TRACE = 'TRACE';
35
37 private static $logBuffer = [];
38
40 private static $logBufferSize = 50;
41
43 private static $shutdownRegistered = false;
44
46 private static $logStats = [
47 'buffered' => 0,
48 'flushed' => 0,
49 'writes' => 0,
50 ];
51
58 public static function init()
59 {
60 if (!self::$shutdownRegistered) {
61 register_shutdown_function([__CLASS__, 'flush']);
62 self::$shutdownRegistered = true;
63 }
64 }
65
78 private static function write($data, $type, $file = null)
79 {
81
82 // Safety check: if globals aren't initialised, fall back to error_log
83 if (!isset($bearsamppRoot) || !isset($bearsamppCore) || !isset($bearsamppConfig)) {
84 error_log('[' . $type . '] ' . $data);
85 return;
86 }
87
88 // Lazily register the shutdown handler if init() was not called explicitly
89 self::init();
90
91 // Resolve default file path only when the caller did not supply one
92 if ($file === null) {
93 $file = $type === self::ERROR
94 ? $bearsamppRoot->getErrorLogFilePath()
95 : $bearsamppRoot->getLogFilePath();
96
97 if (!$bearsamppRoot->isRoot()) {
98 $file = $bearsamppRoot->getHomepageLogFilePath();
99 }
100 }
101
102 $verbose = [];
103 $verbose[Config::VERBOSE_SIMPLE] = $type === self::ERROR || $type === self::WARNING;
104 $verbose[Config::VERBOSE_REPORT] = $verbose[Config::VERBOSE_SIMPLE] || $type === self::INFO;
105 $verbose[Config::VERBOSE_DEBUG] = $verbose[Config::VERBOSE_REPORT] || $type === self::DEBUG;
106 $verbose[Config::VERBOSE_TRACE] = $verbose[Config::VERBOSE_DEBUG] || $type === self::TRACE;
107
108 $writeLog = false;
109 if ($bearsamppConfig->getLogsVerbose() === Config::VERBOSE_SIMPLE && $verbose[Config::VERBOSE_SIMPLE]) {
110 $writeLog = true;
111 } elseif ($bearsamppConfig->getLogsVerbose() === Config::VERBOSE_REPORT && $verbose[Config::VERBOSE_REPORT]) {
112 $writeLog = true;
113 } elseif ($bearsamppConfig->getLogsVerbose() === Config::VERBOSE_DEBUG && $verbose[Config::VERBOSE_DEBUG]) {
114 $writeLog = true;
115 } elseif ($bearsamppConfig->getLogsVerbose() === Config::VERBOSE_TRACE && $verbose[Config::VERBOSE_TRACE]) {
116 $writeLog = true;
117 }
118
119 if ($writeLog) {
120 self::$logBuffer[] = [
121 'file' => $file,
122 'data' => $data,
123 'type' => $type,
124 'time' => time(),
125 ];
126 self::$logStats['buffered']++;
127
128 // Flush immediately for errors, or when the buffer is full
129 if ($type === self::ERROR || count(self::$logBuffer) >= self::$logBufferSize) {
130 self::flush();
131 }
132 }
133 }
134
142 public static function flush()
143 {
144 if (empty(self::$logBuffer)) {
145 return;
146 }
147
148 global $bearsamppCore;
149
150 // If the core global is gone (e.g. during an abnormal shutdown), fall back to error_log
151 if (!isset($bearsamppCore)) {
152 foreach (self::$logBuffer as $log) {
153 error_log('[' . date('Y-m-d H:i:s', $log['time']) . '] [' . $log['type'] . '] ' . $log['data']);
154 }
155 self::$logStats['flushed'] += count(self::$logBuffer);
156 self::$logBuffer = [];
157 return;
158 }
159
160 // Group logs by destination file
161 $logsByFile = [];
162 foreach (self::$logBuffer as $log) {
163 $logsByFile[$log['file']][] = $log;
164 }
165
166 foreach ($logsByFile as $file => $logs) {
167 $content = '';
168 foreach ($logs as $log) {
169 $content .= '[' . date('Y-m-d H:i:s', $log['time']) . '] # ' .
170 APP_TITLE . ' ' . $bearsamppCore->getAppVersion() . ' # ' .
171 $log['type'] . ': ' . $log['data'] . PHP_EOL;
172 }
173
174 $written = @file_put_contents($file, $content, FILE_APPEND | LOCK_EX);
175 if ($written === false) {
176 // File write failed — ensure entries are not silently lost
177 foreach ($logs as $log) {
178 error_log('[' . $log['type'] . '] ' . $log['data'] . ' (target: ' . $file . ')');
179 }
180 }
181
182 self::$logStats['writes']++;
183 }
184
185 self::$logStats['flushed'] += count(self::$logBuffer);
186 self::$logBuffer = [];
187 }
188
195 public static function reset()
196 {
197 self::$logBuffer = [];
198 self::$shutdownRegistered = false;
199 self::$logStats = [
200 'buffered' => 0,
201 'flushed' => 0,
202 'writes' => 0,
203 ];
204 }
205
211 public static function getStats()
212 {
213 return self::$logStats;
214 }
215
222 public static function setBufferSize($size)
223 {
224 if ($size > 0 && $size <= 1000) {
225 self::$logBufferSize = $size;
226 }
227 }
228
234 public static function getBufferSize()
235 {
236 return self::$logBufferSize;
237 }
238
244 public static function separator()
245 {
246 global $bearsamppRoot;
247
248 $logs = [
249 $bearsamppRoot->getLogFilePath(),
250 $bearsamppRoot->getErrorLogFilePath(),
251 $bearsamppRoot->getServicesLogFilePath(),
252 $bearsamppRoot->getRegistryLogFilePath(),
253 $bearsamppRoot->getStartupLogFilePath(),
254 $bearsamppRoot->getBatchLogFilePath(),
255 $bearsamppRoot->getWinbinderLogFilePath(),
256 ];
257
258 $separator = '========================================================================================' . PHP_EOL;
259 foreach ($logs as $log) {
260 if (!file_exists($log)) {
261 continue;
262 }
263 $logContent = @file_get_contents($log);
264 if ($logContent !== false && !str_ends_with($logContent, $separator)) {
265 file_put_contents($log, $separator, FILE_APPEND);
266 }
267 }
268 }
269
276 public static function trace($data, $file = null)
277 {
278 self::write($data, self::TRACE, $file);
279 }
280
287 public static function debug($data, $file = null)
288 {
289 self::write($data, self::DEBUG, $file);
290 }
291
298 public static function info($data, $file = null)
299 {
300 self::write($data, self::INFO, $file);
301 }
302
309 public static function warning($data, $file = null)
310 {
311 self::write($data, self::WARNING, $file);
312 }
313
321 public static function error($data, $file = null)
322 {
323 self::write($data, self::ERROR, $file);
324 }
325
331 public static function initClass($classInstance)
332 {
333 self::trace('Init ' . get_class($classInstance));
334 }
335
341 public static function reloadClass($classInstance)
342 {
343 self::trace('Reload ' . get_class($classInstance));
344 }
345}
global $bearsamppRoot
global $bearsamppCore
const VERBOSE_REPORT
const VERBOSE_SIMPLE
const VERBOSE_TRACE
const VERBOSE_DEBUG
static flush()
static setBufferSize($size)
static $logStats
Definition class.log.php:46
static info($data, $file=null)
static getBufferSize()
static reset()
static debug($data, $file=null)
static getStats()
static $logBuffer
Definition class.log.php:37
const DEBUG
Definition class.log.php:33
const ERROR
Definition class.log.php:30
static warning($data, $file=null)
static $logBufferSize
Definition class.log.php:40
static reloadClass($classInstance)
static write($data, $type, $file=null)
Definition class.log.php:78
static init()
Definition class.log.php:58
static $shutdownRegistered
Definition class.log.php:43
static trace($data, $file=null)
static error($data, $file=null)
const WARNING
Definition class.log.php:31
const TRACE
Definition class.log.php:34
static separator()
const INFO
Definition class.log.php:32
static initClass($classInstance)
global $bearsamppConfig
Definition homepage.php:41
const APP_TITLE
Definition root.php:13