更新
This commit is contained in:
+25
-14
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user