更新
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -25,10 +26,18 @@ func (l *Logger) delete() {
|
||||
}()
|
||||
// 先清掉「压缩完成但源文件没删掉」留下的冗余文件
|
||||
if err := l.healCompressedLeftovers(); err != nil {
|
||||
fmt.Println(err)
|
||||
l.reportError(err)
|
||||
}
|
||||
// 清掉崩溃/被杀留下的半截压缩临时文件
|
||||
if err := l.sweepStaleTemps(); err != nil {
|
||||
l.reportError(err)
|
||||
}
|
||||
// 释放长时间不活跃的句柄(通道名多变的场景会积累大量 fd + 32KB 缓冲)
|
||||
if err := l.sweepIdleHandles(); err != nil {
|
||||
l.reportError(err)
|
||||
}
|
||||
if err := l.walkAndDel(); err != nil {
|
||||
fmt.Println(err)
|
||||
l.reportError(err)
|
||||
}
|
||||
}()
|
||||
case <-l.ctx.Done():
|
||||
@@ -46,11 +55,12 @@ func (l *Logger) walkAndDel() error {
|
||||
}
|
||||
|
||||
deadline := time.Now().AddDate(0, 0, -l.option.days)
|
||||
live := l.livePaths()
|
||||
|
||||
err := filepath.Walk(l.option.dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
// 单个文件出错不应该中断整个遍历
|
||||
fmt.Println(err)
|
||||
l.reportError(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -68,19 +78,70 @@ func (l *Logger) walkAndDel() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 绝不能删正在被本实例写入的文件:
|
||||
// Linux 上删除已打开的文件会成功,之后所有写入都进了「已 unlink 的 inode」,
|
||||
// 日志对外彻底不可见、空间也不释放;Windows 上则会一直删不掉刷报错
|
||||
if live[path] {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 删除文件 / 空目录
|
||||
if rerr := os.Remove(path); rerr != nil {
|
||||
// 正被写入的文件在 Windows 上删不掉,等下一轮再试,不要终止遍历
|
||||
fmt.Println("删除文件失败", path, rerr)
|
||||
// 正被外部进程占用的文件删不掉,等下一轮再试,不要终止遍历
|
||||
l.reportError(fmt.Errorf("loggerx: 删除文件失败 %s: %w", path, rerr))
|
||||
return nil
|
||||
}
|
||||
fmt.Println("删除文件", path)
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// livePaths 当前所有打开着的日志文件路径集合
|
||||
func (l *Logger) livePaths() map[string]bool {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
|
||||
live := make(map[string]bool, len(l.filePath))
|
||||
for _, f := range l.filePath {
|
||||
if f != nil && f.fileName != "" {
|
||||
live[f.fileName] = true
|
||||
}
|
||||
}
|
||||
return live
|
||||
}
|
||||
|
||||
// staleTempAge 多久没动静的临时文件算垃圾
|
||||
const staleTempAge = time.Hour
|
||||
|
||||
// sweepStaleTemps 清理崩溃/被杀时留下的半截压缩临时文件
|
||||
//
|
||||
// gzipFile 只在自己的错误分支里删 .gz.tmp,进程被 SIGKILL 时来不及删,
|
||||
// 而这些文件的扩展名既不是 .log 也不是 .gz,原来的清理逻辑完全看不到它们
|
||||
func (l *Logger) sweepStaleTemps() error {
|
||||
cutoff := time.Now().Add(-staleTempAge)
|
||||
|
||||
var errs []error
|
||||
_ = filepath.Walk(l.option.dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil || info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if !strings.HasSuffix(path, gzipTempSuffix) {
|
||||
return nil
|
||||
}
|
||||
// 留一点余量:可能正好有一个压缩任务在写它
|
||||
if info.ModTime().After(cutoff) {
|
||||
return nil
|
||||
}
|
||||
if rerr := os.Remove(path); rerr != nil {
|
||||
errs = append(errs, fmt.Errorf("loggerx: 清理半截压缩文件失败 %s: %w", path, rerr))
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return joinErrors(errs)
|
||||
}
|
||||
|
||||
// 判断空文件夹
|
||||
func isEmptyDir(path string) bool {
|
||||
files, err := os.ReadDir(path)
|
||||
|
||||
Reference in New Issue
Block a user