正在显示
1 个修改的文件
包含
127 行增加
和
0 行删除
jiaoyin/StreamLogger.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace Jiaoyin; | ||
| 4 | + | ||
| 5 | +class StreamLogger | ||
| 6 | +{ | ||
| 7 | + protected string $basePath; | ||
| 8 | + protected array $buffer = []; | ||
| 9 | + protected int $flushSize = 50; // 缓冲条数 | ||
| 10 | + protected int $flushInterval = 1000; // 最长缓存时间(毫秒) | ||
| 11 | + protected array $lastWrite = []; // 每个 channel 上次写盘时间戳(毫秒) | ||
| 12 | + | ||
| 13 | + public function __construct(string $basePath = null) | ||
| 14 | + { | ||
| 15 | + // 优先使用传入的 | ||
| 16 | + if ($basePath) { | ||
| 17 | + $this->basePath = rtrim($basePath, '/'); | ||
| 18 | + } else { | ||
| 19 | + // 没传才自动查找项目根目录 | ||
| 20 | + $this->basePath = $this->findProjectRoot() . '/logs'; | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + if (!is_dir($this->basePath)) { | ||
| 24 | + mkdir($this->basePath, 0777, true); | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + // 启动定时器(仅 Swoole 环境有效) | ||
| 28 | + if (class_exists(\Swoole\Timer::class)) { | ||
| 29 | + \Swoole\Timer::tick(1000, function () { | ||
| 30 | + $this->flushByInterval(); | ||
| 31 | + }); | ||
| 32 | + } | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + public function log($channel, $content): void | ||
| 36 | + { | ||
| 37 | + $time = $this->timeFormat('ms'); | ||
| 38 | + $msg = "[$time] " . (is_array($content) | ||
| 39 | + ? json_encode($content, JSON_UNESCAPED_UNICODE) | ||
| 40 | + : $content | ||
| 41 | + ); | ||
| 42 | + | ||
| 43 | + $this->buffer[$channel][] = $msg; | ||
| 44 | + | ||
| 45 | + // 更新最后写入时间 | ||
| 46 | + $this->lastWrite[$channel] = $this->nowMs(); | ||
| 47 | + | ||
| 48 | + // 数量超限:立即 flush | ||
| 49 | + if (count($this->buffer[$channel]) >= $this->flushSize) { | ||
| 50 | + $this->flush($channel); | ||
| 51 | + } | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + public function error($channel, $content): void | ||
| 55 | + { | ||
| 56 | + $this->log($channel . '_error', $content); | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + // 定时器触发:超过时间未写盘则 flush | ||
| 60 | + protected function flushByInterval(): void | ||
| 61 | + { | ||
| 62 | + $now = $this->nowMs(); | ||
| 63 | + | ||
| 64 | + foreach ($this->buffer as $channel => $list) { | ||
| 65 | + if (empty($list)) continue; | ||
| 66 | + | ||
| 67 | + $last = $this->lastWrite[$channel] ?? 0; | ||
| 68 | + | ||
| 69 | + if ($now - $last >= $this->flushInterval) { | ||
| 70 | + $this->flush($channel); | ||
| 71 | + } | ||
| 72 | + } | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + public function flush($channel = null): void | ||
| 76 | + { | ||
| 77 | + if ($channel === null) { | ||
| 78 | + foreach ($this->buffer as $ch => $_) { | ||
| 79 | + $this->writeChannel($ch); | ||
| 80 | + } | ||
| 81 | + } else { | ||
| 82 | + $this->writeChannel($channel); | ||
| 83 | + } | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + protected function writeChannel($channel): void | ||
| 87 | + { | ||
| 88 | + if (empty($this->buffer[$channel])) return; | ||
| 89 | + | ||
| 90 | + $file = "{$this->basePath}/{$channel}.log"; | ||
| 91 | + | ||
| 92 | + file_put_contents($file, implode(PHP_EOL, $this->buffer[$channel]) . PHP_EOL, FILE_APPEND); | ||
| 93 | + | ||
| 94 | + $this->buffer[$channel] = []; | ||
| 95 | + $this->lastWrite[$channel] = $this->nowMs(); | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + protected function findProjectRoot(): string | ||
| 99 | + { | ||
| 100 | + $dir = __DIR__; | ||
| 101 | + while ($dir !== '/' && $dir !== '.') { | ||
| 102 | + if (basename($dir) === 'vendor') { | ||
| 103 | + return dirname($dir); // vendor 的父级 → 项目根目录 | ||
| 104 | + } | ||
| 105 | + if (is_dir($dir . '/vendor')) { | ||
| 106 | + return $dir; // 当前目录就是项目根目录 | ||
| 107 | + } | ||
| 108 | + $dir = dirname($dir); // 继续往上 | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + return __DIR__; // 兜底 | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + protected function nowMs(): int | ||
| 115 | + { | ||
| 116 | + return (int)(microtime(true) * 1000); | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + protected function timeFormat($type = 's', $format = 'Y-m-d H:i:s'): string | ||
| 120 | + { | ||
| 121 | + [$ms, $sec] = explode(' ', microtime()); | ||
| 122 | + $t = date($format, $sec); | ||
| 123 | + return $type === 'ms' | ||
| 124 | + ? $t . '.' . sprintf('%03d', floor($ms * 1000)) | ||
| 125 | + : $t; | ||
| 126 | + } | ||
| 127 | +} |
-
请 注册 或 登录 后发表评论