Logger.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. <?php
  2. /*
  3. * Part of Shuzanne - An extensible sequel to an open-source imageboard.
  4. *
  5. * @package Shuzanne
  6. * @author MisleadingName, Shuzanne Contributors
  7. * @license MPL v2.0
  8. *
  9. * This Source Code Form is subject to the terms of the Mozilla Public
  10. * License, v. 2.0. If a copy of the MPL was not distributed with this
  11. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
  12. */
  13. namespace app;
  14. class Logger
  15. {
  16. private const LOG_FILE = "rajesh.log";
  17. private const DEFAULT_MAX_SIZE_KB = 5000; // 5MB default max size
  18. private static ?string $logPath = null;
  19. private static LogLevel $minLevel = LogLevel::DBUG;
  20. private static int $maxSizeKB = self::DEFAULT_MAX_SIZE_KB;
  21. /**
  22. * Initialize the logger
  23. */
  24. public static function Initialize(): void
  25. {
  26. self::$logPath = APP_ROOT . "/logs/" . self::LOG_FILE;
  27. register_shutdown_function(function () {
  28. $error = error_get_last();
  29. if ($error && in_array($error["type"], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
  30. self::Fatal("PHP Fatal Error: {$error["message"]} in {$error["file"]} on line {$error["line"]}");
  31. }
  32. });
  33. // Register hook for plugin access
  34. // Hooks::Register("Shuzanne.LoggerWrite", function ($data) {
  35. // self::Write($data["level"], $data["message"], $data["context"] ?? []);
  36. // return $data;
  37. // });
  38. self::Info("Logger initialized. Log file: " . self::$logPath);
  39. }
  40. /**
  41. * Set minimum log level
  42. */
  43. public static function SetMinLevel(LogLevel $level): void
  44. {
  45. self::$minLevel = $level;
  46. }
  47. /**
  48. * Set maximum log file size in kilobytes before rotation
  49. */
  50. public static function SetMaxLogSizeKB(int $sizeKB): void
  51. {
  52. self::$maxSizeKB = max(1, $sizeKB); // Ensure at least 1KB
  53. self::CheckRotation();
  54. }
  55. /**
  56. * Write a message to the log file
  57. */
  58. public static function Write(LogLevel $level, string $message, array $context = [], string $file = "Unknown", int $line = 0): void
  59. {
  60. if ($level->value < self::$minLevel->value) {
  61. return;
  62. }
  63. if (self::$logPath === null) {
  64. self::Initialize();
  65. }
  66. self::CheckRotation();
  67. foreach ($context as $key => $value) {
  68. if (is_array($value) || is_object($value)) {
  69. $value = json_encode($value);
  70. }
  71. $message = str_replace("{{$key}}", $value, $message);
  72. }
  73. $timestamp = date("Y-m-d H:i:s");
  74. $levelName = $level->name;
  75. $file = basename($file);
  76. $formattedMessage = "[$timestamp] <$file:$line> [$levelName] $message" . PHP_EOL;
  77. file_put_contents(self::$logPath, $formattedMessage, FILE_APPEND);
  78. }
  79. /**
  80. * Check if log rotation is needed and rotate if necessary
  81. */
  82. private static function CheckRotation(): void
  83. {
  84. if (!file_exists(self::$logPath)) {
  85. return;
  86. }
  87. $sizeKB = filesize(self::$logPath) / 1024;
  88. if ($sizeKB >= self::$maxSizeKB) {
  89. self::RotateLog();
  90. }
  91. }
  92. /**
  93. * Rotate the log file
  94. */
  95. public static function RotateLog(): bool
  96. {
  97. if (!file_exists(self::$logPath)) {
  98. return false;
  99. }
  100. $timestamp = date("Y-m-d_H-i-s");
  101. $rotatedFileName = "old_logs_{$timestamp}.log.gz";
  102. $rotatedFilePath = dirname(self::$logPath) . DIRECTORY_SEPARATOR . $rotatedFileName;
  103. $logContent = file_get_contents(self::$logPath);
  104. $compressedContent = gzencode($logContent, 9);
  105. if (file_put_contents($rotatedFilePath, $compressedContent) === false) {
  106. self::Error("Failed to create rotated log file: {$rotatedFilePath}");
  107. return false;
  108. }
  109. if (file_put_contents(self::$logPath, "") === false) {
  110. self::Error("Failed to clear log file after rotation");
  111. return false;
  112. }
  113. self::Info("Old log rotated to {$rotatedFileName}");
  114. return true;
  115. }
  116. /**
  117. * Debug level message
  118. */
  119. public static function Debug(string $message, array $context = []): void
  120. {
  121. $key = array_search(__FUNCTION__, array_column(debug_backtrace(), "function"));
  122. $file = debug_backtrace()[$key]["file"];
  123. $line = debug_backtrace()[$key]["line"];
  124. self::Write(LogLevel::DBUG, $message, $context, $file, $line);
  125. }
  126. /**
  127. * Info level message
  128. */
  129. public static function Info(string $message, array $context = []): void
  130. {
  131. $key = array_search(__FUNCTION__, array_column(debug_backtrace(), "function"));
  132. $file = debug_backtrace()[$key]["file"];
  133. $line = debug_backtrace()[$key]["line"];
  134. self::Write(LogLevel::INFO, $message, $context, $file, $line);
  135. }
  136. /**
  137. * Warning level message
  138. */
  139. public static function Warn(string $message, array $context = []): void
  140. {
  141. $key = array_search(__FUNCTION__, array_column(debug_backtrace(), "function"));
  142. $file = debug_backtrace()[$key]["file"];
  143. $line = debug_backtrace()[$key]["line"];
  144. self::Write(LogLevel::WARN, $message, $context, $file, $line);
  145. }
  146. /**
  147. * Error level message
  148. */
  149. public static function Error(string $message, array $context = []): void
  150. {
  151. $key = array_search(__FUNCTION__, array_column(debug_backtrace(), "function"));
  152. $file = debug_backtrace()[$key]["file"];
  153. $line = debug_backtrace()[$key]["line"];
  154. self::Write(LogLevel::EROR, $message, $context, $file, $line);
  155. }
  156. /**
  157. * Fatal level message
  158. */
  159. public static function Fatal(string $message, array $context = []): void
  160. {
  161. $key = array_search(__FUNCTION__, array_column(debug_backtrace(), "function"));
  162. $file = debug_backtrace()[$key]["file"];
  163. $line = debug_backtrace()[$key]["line"];
  164. self::Write(LogLevel::FTAL, $message, $context, $file, $line);
  165. }
  166. /**
  167. * Get the log file path
  168. */
  169. public static function GetLogPath(): string
  170. {
  171. if (self::$logPath === null) {
  172. self::Initialize();
  173. }
  174. return self::$logPath;
  175. }
  176. /**
  177. * Clear the log file
  178. */
  179. public static function Clear(): bool
  180. {
  181. if (self::$logPath === null) {
  182. self::Initialize();
  183. }
  184. return file_put_contents(self::$logPath, "") !== false;
  185. }
  186. }
  187. enum LogLevel: int
  188. {
  189. case DBUG = 0;
  190. case INFO = 1;
  191. case WARN = 2;
  192. case EROR = 3;
  193. case FTAL = 4;
  194. public static function tryFromName(string $name): ?LogLevel
  195. {
  196. return array_find(self::cases(), fn($case) => $case->name === $name);
  197. }
  198. }