竹简文档
日志系统

日志切割

RotatingWriter 实现按大小切割和按天归档

日志切割

RotatingWriter 是支持自动切割的日志写入器,实现 io.Writer 接口,当文件大小超过阈值时自动切割,并在每天凌晨自动归档前一天的日志。

RotatingWriter

rotator.go
type RotatingWriter struct {
    mu          sync.Mutex
    file        *os.File   // 当前写入的文件
    dir         string     // 日志目录
    baseName    string     // 基础文件名
    ext         string     // 扩展名
    maxSize     int64      // 最大文件大小
    currentSize int64      // 当前文件大小
    currentDate string     // 当前日期
}

RotatorConfig

rotator.go
type RotatorConfig struct {
    Dir      string // 日志目录
    BaseName string // 基础文件名 (如 "log")
    Ext      string // 扩展名 (如 ".log")
    MaxSize  int64  // 最大文件大小 (字节),默认 10MB
}

配置说明

字段

类型

NewRotatingWriter

创建日志切割写入器:

rotator.go
func NewRotatingWriter(config RotatorConfig) (*RotatingWriter, error)

示例:

rotator, err := xLog.NewRotatingWriter(xLog.RotatorConfig{
    Dir:      ".logs",
    BaseName: "log",
    Ext:      ".log",
    MaxSize:  10 * 1024 * 1024, // 10MB
})
if err != nil {
    panic(err)
}

文件命名规则

当前日志文件

.logs/log.log          # 当前正在写入的文件

切割后的文件

.logs/log.0.log        # 最早的切割文件
.logs/log.1.log        # 较新的切割文件
.logs/log.2.log        # 最新的切割文件

规则: 索引数字越大,文件越新。

归档文件

.logs/logger-2024-01-14.tar.gz   # 2024-01-14 的归档
.logs/logger-2024-01-15.tar.gz   # 2024-01-15 的归档

切割流程

切割逻辑:

rotator.go
func (w *RotatingWriter) rotate() error {
    // 1. 关闭当前文件
    w.file.Close()

    // 2. 查找最大索引
    maxIndex := w.findMaxRotatedIndex()
    nextIndex := maxIndex + 1

    // 3. 重命名当前文件
    os.Rename(w.currentFilePath(), w.rotatedFilePath(nextIndex))

    // 4. 创建新文件
    w.currentSize = 0
    return w.openFile()
}

归档流程

每天 00:00:05 自动执行归档:

flowchart TD
    A[00:00:05 触发] --> B{归档文件已存在?}
    B -->|是| C[跳过]
    B -->|否| D[收集切割文件]
    D --> E{有文件需要归档?}
    E -->|否| C
    E -->|是| F[创建 tar.gz]
    F --> G[删除已归档文件]

归档逻辑:

rotator.go
func (w *RotatingWriter) archiveYesterday() {
    // 归档文件名: logger-yyyy-MM-dd.tar.gz
    yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
    archiveName := fmt.Sprintf("logger-%s.tar.gz", yesterday)

    // 收集需要归档的文件 (log.0.log, log.1.log, ...)
    files := w.collectFilesToArchive()

    // 创建 tar.gz 归档
    w.createTarGz(archivePath, files)

    // 删除已归档的文件
    for _, file := range files {
        os.Remove(file)
    }
}

目录结构示例

.logs/
├── log.log                      # 当前日志文件
├── log.0.log                    # 切割文件 (今天)
├── log.1.log                    # 切割文件 (今天)
├── logger-2024-01-13.tar.gz     # 归档 (前天)
└── logger-2024-01-14.tar.gz     # 归档 (昨天)

使用示例

基础使用

rotator, err := xLog.NewRotatingWriter(xLog.RotatorConfig{
    Dir:      ".logs",
    BaseName: "app",
    MaxSize:  5 * 1024 * 1024, // 5MB
})
if err != nil {
    panic(err)
}
defer rotator.Close()

// 配合 LogHandler 使用
handler := xLog.NewLogHandler(xLog.HandlerConfig{
    Console: os.Stdout,
    File:    rotator,
    Level:   slog.LevelInfo,
})

自定义配置

// 大型应用:更大的文件和更频繁的切割
rotator, _ := xLog.NewRotatingWriter(xLog.RotatorConfig{
    Dir:      "/var/log/myapp",
    BaseName: "service",
    Ext:      ".json",
    MaxSize:  50 * 1024 * 1024, // 50MB
})
// 小型应用:较小的文件
rotator, _ := xLog.NewRotatingWriter(xLog.RotatorConfig{
    Dir:      ".logs",
    BaseName: "debug",
    Ext:      ".log",
    MaxSize:  1 * 1024 * 1024, // 1MB
})

线程安全

RotatingWriter 使用 sync.Mutex 保证线程安全:

rotator.go
func (w *RotatingWriter) Write(p []byte) (n int, err error) {
    w.mu.Lock()
    defer w.mu.Unlock()

    // 检查是否需要切割
    if w.currentSize+int64(len(p)) > w.maxSize {
        w.rotate()
    }

    // 写入数据
    n, err = w.file.Write(p)
    w.currentSize += int64(n)
    return n, err
}

下一步

On this page