This commit is contained in:
yun
2026-09-14 00:14:30 +08:00
parent 316c6420a7
commit 7e1b474823
20 changed files with 1885 additions and 166 deletions
+25 -14
View File
@@ -19,18 +19,6 @@ import (
// 2. 把文件改名成 名字_N.log(先改名,保证任何时刻文件都存在,崩溃也不丢数据)
// 3. 交给后台协程压缩成 名字_N.log.gz,压缩成功后再删掉未压缩文件
// nextIndex 返回下一个归档序号
// 首次调用会先扫描目录里已有的 _N.log / _N.log.gz,避免覆盖上次运行的归档
func (l *Logger) nextIndex() int {
l.idxOnce.Do(func() {
l.idx = l.maxIndexOnDisk()
})
l.idxMu.Lock()
defer l.idxMu.Unlock()
l.idx++
return l.idx
}
// maxIndexOnDisk 扫描日志目录,找出已经用掉的最大序号
func (l *Logger) maxIndexOnDisk() int {
max := 0
@@ -74,8 +62,11 @@ func (l *Logger) archive(base, fileName string) (string, error) {
for try := 0; try < 1000; try++ {
idx := l.nextIndex()
candidate := filepath.Join(dir, fmt.Sprintf("%s_%d.log", base, idx))
if _, err := os.Stat(candidate); err == nil {
continue // 已存在,换一个序号
// 占用判断必须同时看 .log 和 .log.gz
// 压缩成功后只剩 .gz,只看 .log 会以为序号空着,
// 于是归档名撞上已有归档,后面 gzip 重命名会把老归档覆盖掉
if indexInUse(base, idx) {
continue
}
if err := os.Rename(fileName, candidate); err != nil {
return "", fmt.Errorf("归档日志文件失败 %s: %w", fileName, err)
@@ -85,6 +76,18 @@ func (l *Logger) archive(base, fileName string) (string, error) {
return "", fmt.Errorf("归档日志文件失败 %s: 找不到可用序号", fileName)
}
// indexInUse 序号 idx 是否已被占用(.log 或 .log.gz 任一存在即算占用)
func indexInUse(base string, idx int) bool {
name := fmt.Sprintf("%s_%d.log", strings.TrimSuffix(base, ".log"), idx)
if _, err := os.Stat(name); err == nil {
return true
}
if _, err := os.Stat(name + ".gz"); err == nil {
return true
}
return false
}
// scheduleCompress 把压缩任务丢到后台,不阻塞写入路径
// 不压缩时什么也不做,归档文件保留为 .log
func (l *Logger) scheduleCompress(path string) {
@@ -101,6 +104,9 @@ func (l *Logger) scheduleCompress(path string) {
}()
}
// gzipTempSuffix 压缩临时文件后缀
const gzipTempSuffix = ".gz.tmp"
// gzipFile 把 src 压缩成 src.gz,成功后删除 src
// 先写临时文件再改名,避免半截 .gz 被当成完整归档
func gzipFile(src string, level int) error {
@@ -151,6 +157,11 @@ func gzipFile(src string, level int) error {
closeIn() // 删除源文件之前一定要先释放读句柄
dst := src + ".gz"
// 绝不覆盖已有归档:序号复用或并发写入时,覆盖等于直接销毁历史日志
if _, err := os.Stat(dst); err == nil {
_ = os.Remove(tmp)
return fmt.Errorf("归档目标已存在,拒绝覆盖: %s", dst)
}
if err := os.Rename(tmp, dst); err != nil {
_ = os.Remove(tmp)
return err