SizeRotatingDailyHandler.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. <?php
  2. namespace UCore\Logging;
  3. use Monolog\Handler\StreamHandler;
  4. use Monolog\LogRecord;
  5. use Monolog\Level;
  6. /**
  7. * 支持文件大小限制的每日轮转日志处理器
  8. *
  9. * 功能特点:
  10. * 1. 初始日志文件使用基础文件名(如:laravel.log)
  11. * 2. 当文件大小超过限制或超过60秒未修改时,自动分割为备份文件(如:laravel-2025-05-26-1.log)
  12. * 3. 支持设置最大文件数量,自动清理旧文件
  13. */
  14. class SizeRotatingDailyHandler extends StreamHandler
  15. {
  16. /**
  17. * 最大文件大小(字节)
  18. *
  19. * @var int
  20. */
  21. protected int $maxFileSize;
  22. /**
  23. * 基础文件名
  24. *
  25. * @var string
  26. */
  27. protected string $baseFilename;
  28. /**
  29. * 最大文件数量
  30. *
  31. * @var int
  32. */
  33. protected int $maxFiles;
  34. /**
  35. * 文件切割间隔秒
  36. */
  37. protected int $diffTimes = 60;
  38. /**
  39. * 构造函数
  40. *
  41. * @param string $filename 日志文件路径
  42. * @param int $maxFiles 最大文件数量
  43. * @param int|string|Level $level 日志级别
  44. * @param bool $bubble 是否冒泡
  45. * @param int|null $filePermission 文件权限
  46. * @param bool $useLocking 是否使用文件锁
  47. * @param int $maxFileSize 最大文件大小(字节),默认100MB
  48. */
  49. public function __construct(
  50. string $filename,
  51. int $maxFiles = 0,
  52. int|string|Level $level = Level::Debug,
  53. bool $bubble = true,
  54. ?int $filePermission = null,
  55. bool $useLocking = false,
  56. int $maxFileSize = 104857600 // 100MB
  57. ) {
  58. $this->maxFileSize = $maxFileSize;
  59. $this->baseFilename = $filename;
  60. $this->maxFiles = $maxFiles;
  61. // 获取当前应该使用的文件名
  62. $currentFilename = $this->getCurrentFilename();
  63. parent::__construct($currentFilename, $level, $bubble, $filePermission, $useLocking);
  64. }
  65. /**
  66. * 获取当前应该使用的文件名
  67. *
  68. * @return string
  69. */
  70. protected function getCurrentFilename(): string
  71. {
  72. // 始终返回基础文件名
  73. // 文件大小检查和轮转在写入时处理
  74. return $this->baseFilename;
  75. }
  76. /**
  77. * 获取带时间戳的文件名
  78. *
  79. * @return string
  80. */
  81. protected function getTimedFilename(): string
  82. {
  83. $fileInfo = pathinfo($this->baseFilename);
  84. $timedFilename = $fileInfo['dirname'] . '/' . $fileInfo['filename'] . '-' . date('Y-m-d');
  85. if (isset($fileInfo['extension'])) {
  86. $timedFilename .= '.' . $fileInfo['extension'];
  87. }
  88. return $timedFilename;
  89. }
  90. /**
  91. * 获取下一个可用的备份文件名
  92. *
  93. * @param string $currentFilename 当前文件名
  94. * @return string
  95. */
  96. protected function getNextBackupFilename(string $currentFilename): string
  97. {
  98. $fileInfo = pathinfo($currentFilename);
  99. $baseName = $fileInfo['filename'];
  100. $extension = isset($fileInfo['extension']) ? '.' . $fileInfo['extension'] : '';
  101. $directory = $fileInfo['dirname'].'/size_rotating_daily';
  102. if(!is_dir($directory)){
  103. mkdir($directory);
  104. }
  105. // 为基础文件名添加当前日期
  106. $baseNameWithDate = $baseName . '-' . date('Y-m-d');
  107. $counter = 1;
  108. do {
  109. $backupFilename = $directory . '/' . $baseNameWithDate . '-' . $counter . $extension;
  110. $counter++;
  111. } while (file_exists($backupFilename));
  112. return $backupFilename;
  113. }
  114. /**
  115. * 重写写入方法,添加文件大小和时间检查
  116. *
  117. * @param LogRecord $record
  118. * @return void
  119. */
  120. protected function write(LogRecord $record): void
  121. {
  122. // 先调用父类写入方法
  123. parent::write($record);
  124. // 在写入后检查是否需要进行分割
  125. if ($this->url && file_exists($this->url) && $this->shouldRotate()) {
  126. // 进行文件分割
  127. $this->rotateDueToSizeOrTime();
  128. }
  129. }
  130. /**
  131. * 检查是否需要进行轮转
  132. *
  133. * @return bool
  134. */
  135. protected function shouldRotate(): bool
  136. {
  137. // 检查文件大小是否超过限制
  138. if (filesize($this->url) >= $this->maxFileSize) {
  139. return true;
  140. }
  141. // 检查文件最后修改时间是否超过60秒
  142. $lastModified = filemtime($this->url);
  143. if ($lastModified && (time() - $lastModified) >= $this->diffTimes) {
  144. return true;
  145. }
  146. return false;
  147. }
  148. /**
  149. * 由于文件大小超限或时间超限而进行轮转
  150. *
  151. * @return void
  152. */
  153. protected function rotateDueToSizeOrTime(): void
  154. {
  155. // 关闭当前文件句柄
  156. if ($this->stream) {
  157. fclose($this->stream);
  158. $this->stream = null;
  159. }
  160. // 获取备份文件名
  161. $backupFilename = $this->getNextBackupFilename($this->url);
  162. // 将当前文件重命名为备份文件
  163. if (file_exists($this->url)) {
  164. rename($this->url, $backupFilename);
  165. }
  166. // 继续使用原始文件名,下次写入时会创建新的空文件
  167. // 由于 $this->stream 已经设置为 null,父类的 write 方法会重新打开文件
  168. }
  169. }