97 lines
3.2 KiB
Go
97 lines
3.2 KiB
Go
package service
|
||
|
||
// ========================================================================
|
||
// MemLog —— 内存日志环形缓冲(/agent/view 面板「实时日志」Tab 数据源)
|
||
// ========================================================================
|
||
// 设计:
|
||
// - main.go 启动时 log.SetOutput(io.MultiWriter(os.Stdout, service.MemLog))
|
||
// 控制台/systemd 日志完全不受影响,只是多复制一份到内存
|
||
// - 环形缓冲 500 行,每行带自增 ID + 时间戳,面板用 since_id 增量拉取
|
||
// (轮询只传新增行,不用每次全量传 500 行)
|
||
// - 为什么不用文件尾随(tail -f 方案):容器/Windows 部署时日志文件路径
|
||
// 不确定,而 log 包的输出流是唯一稳定的挂接点
|
||
//
|
||
// 安全提示:debug 开关(ai_agent_debug_log)打开时日志含请求体(PHI),
|
||
// 因此 /agent/logs 接口必须走全局 Auth,绝不放行
|
||
// ========================================================================
|
||
|
||
import (
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// MemLogEntry 一行日志
|
||
type MemLogEntry struct {
|
||
ID int64 `json:"id"` // 自增 ID(增量拉取的游标)
|
||
Time int64 `json:"time"` // 写入时间(unix 秒)
|
||
Line string `json:"line"` // 日志原文(含 log 包自带的日期前缀)
|
||
}
|
||
|
||
// MemLogWriter 实现 io.Writer,把日志行写入环形缓冲
|
||
//
|
||
// 并发说明:log 包对每次 Print 调用内部加锁后才调 Write,
|
||
// 但我们仍自己加锁——因为 List 的读取和 Write 是并发的
|
||
type MemLogWriter struct {
|
||
mu sync.RWMutex
|
||
items []MemLogEntry // 定长环形数组
|
||
size int // 容量
|
||
count int // 已写入条数(<= size)
|
||
head int // 下一个写入位置
|
||
nextID int64 // 自增 ID
|
||
}
|
||
|
||
// MemLog 全局单例:保留最近 500 行日志
|
||
var MemLog = &MemLogWriter{
|
||
items: make([]MemLogEntry, 500),
|
||
size: 500,
|
||
}
|
||
|
||
// Write 实现 io.Writer
|
||
//
|
||
// log 包每次调用传入完整的一条日志(含结尾 \n),
|
||
// 但保险起见仍按 \n 拆分(防止某些直接写 writer 的多行输出粘连)
|
||
func (w *MemLogWriter) Write(p []byte) (int, error) {
|
||
now := time.Now().Unix()
|
||
w.mu.Lock()
|
||
for _, line := range strings.Split(strings.TrimRight(string(p), "\n"), "\n") {
|
||
if line == "" {
|
||
continue
|
||
}
|
||
w.nextID++
|
||
w.items[w.head] = MemLogEntry{ID: w.nextID, Time: now, Line: line}
|
||
w.head = (w.head + 1) % w.size
|
||
if w.count < w.size {
|
||
w.count++
|
||
}
|
||
}
|
||
w.mu.Unlock()
|
||
return len(p), nil
|
||
}
|
||
|
||
// List 增量拉取日志
|
||
//
|
||
// 参数:
|
||
// - sinceID:只返回 ID > sinceID 的行(0 表示从最旧开始全量拉)
|
||
// - limit :返回条数上限(<=0 时取 200)
|
||
//
|
||
// 返回按 ID 升序(时间正序),面板直接 append 到滚动区尾部
|
||
func (w *MemLogWriter) List(sinceID int64, limit int) []MemLogEntry {
|
||
if limit <= 0 {
|
||
limit = 200
|
||
}
|
||
w.mu.RLock()
|
||
defer w.mu.RUnlock()
|
||
|
||
out := make([]MemLogEntry, 0, min(limit, w.count))
|
||
// 从最旧的一条开始正序遍历(head 指向"下一个写入位",最旧 = head-count)
|
||
start := (w.head - w.count + w.size*2) % w.size
|
||
for i := 0; i < w.count && len(out) < limit; i++ {
|
||
idx := (start + i) % w.size
|
||
if w.items[idx].ID > sinceID {
|
||
out = append(out, w.items[idx])
|
||
}
|
||
}
|
||
return out
|
||
}
|