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