/* Copyright 2025-2026 linux.do Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package logger import ( "io" "sync" ) // LogEntry 日志条目,对应 ring buffer 中的一行日志 type LogEntry struct { Index int `json:"index"` // 全局递增序号 Data string `json:"data"` // 一行日志原文(含换行符) } // LogRingBuffer 固定容量的环形缓冲区,存储最近的日志行 // 支持:追加日志、按 cursor 分页查询、订阅实时推送 type LogRingBuffer struct { mu sync.RWMutex entries []LogEntry cap int head int // 下一条写入的位置 count int // 当前条目数 seq int // 全局递增序号 subscribers map[chan LogEntry]struct{} subMu sync.RWMutex } // NewLogRingBuffer 创建指定容量的日志环形缓冲区 func NewLogRingBuffer(capacity int) *LogRingBuffer { return &LogRingBuffer{ entries: make([]LogEntry, capacity), cap: capacity, subscribers: make(map[chan LogEntry]struct{}), } } // Write 实现 io.Writer 接口,供 zapcore.WriteSyncer 调用 // 按 '\n' 分割为独立行写入 ring buffer func (r *LogRingBuffer) Write(p []byte) (int, error) { if len(p) == 0 { return 0, nil } data := string(p) start := 0 for i := 0; i < len(data); i++ { if data[i] == '\n' { line := data[start:i] start = i + 1 if len(line) > 0 { r.appendLine(line) } } } // 处理最后一行(没有换行符结尾的情况) if start < len(data) && len(data[start:]) > 0 { r.appendLine(data[start:]) } return len(p), nil } // Sync 实现 zapcore.WriteSyncer 接口 func (r *LogRingBuffer) Sync() error { return nil } // appendLine 追加一行日志到 ring buffer 并通知订阅者 func (r *LogRingBuffer) appendLine(line string) { r.mu.Lock() entry := LogEntry{ Index: r.seq, Data: line, } r.entries[r.head] = entry r.head = (r.head + 1) % r.cap if r.count < r.cap { r.count++ } r.seq++ r.mu.Unlock() // 异步通知订阅者 r.subMu.RLock() for ch := range r.subscribers { select { case ch <- entry: default: // 订阅者消费太慢,丢弃(避免阻塞日志写入) } } r.subMu.RUnlock() } // Query 查询历史日志 // cursor=0 表示查询最新日志,cursor>0 表示查询 index < cursor 的更早日志 // limit 为返回条数上限 // 返回日志条目(按 index 升序)和是否有更早的日志 func (r *LogRingBuffer) Query(cursor int, limit int) ([]LogEntry, bool) { r.mu.RLock() defer r.mu.RUnlock() if r.count == 0 { return nil, false } // 计算 ring buffer 中有效条目的范围 // oldest index in ring: head - count (wrapping) oldestPos := (r.head - r.count + r.cap) % r.cap // 将 ring buffer 中的有效条目按顺序收集 ordered := make([]LogEntry, 0, r.count) for i := 0; i < r.count; i++ { pos := (oldestPos + i) % r.cap ordered = append(ordered, r.entries[pos]) } if cursor == 0 { // 查询最新日志:返回最后 limit 条 if len(ordered) <= limit { return ordered, false } return ordered[len(ordered)-limit:], true } // 查询 index < cursor 的更早日志 // 找到 index < cursor 的条目 var cut int for cut = len(ordered); cut > 0; cut-- { if ordered[cut-1].Index < cursor { break } } if cut == 0 { return nil, false } // 返回 cut 之前的最后 limit 条 start := cut - limit if start < 0 { start = 0 } hasMore := start > 0 return ordered[start:cut], hasMore } // Subscribe 订阅实时日志推送 // 返回一个 channel,调用者应 defer Unsubscribe func (r *LogRingBuffer) Subscribe() chan LogEntry { ch := make(chan LogEntry, 64) r.subMu.Lock() r.subscribers[ch] = struct{}{} r.subMu.Unlock() return ch } // Unsubscribe 取消订阅 func (r *LogRingBuffer) Unsubscribe(ch chan LogEntry) { r.subMu.Lock() delete(r.subscribers, ch) r.subMu.Unlock() close(ch) } // 确保 LogRingBuffer 实现 io.Writer 接口 var _ io.Writer = (*LogRingBuffer)(nil)