作者 zed

增加日志分标记写入功能。

<?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;
}
}
... ...