全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-18 8 分钟 ✍️ juanwangdev

Gin 日志输出到文件与轮转

日志文件轮转是生产环境的必备功能,防止日志文件无限增长。

基础文件输出

直接写入文件

Go
import (
    "github.com/sirupsen/logrus"
)

func SetupFileLogger() error {
    // 创建日志目录
    os.MkdirAll("logs", 0755)

    // 打开日志文件
    file, err := os.OpenFile("logs/app.log",
        os.O_CREATE|os.O_WRONLY|os.O_APPEND,
        0644)
    if err != nil {
        return err
    }

    // 同时输出到控制台和文件
    logger.SetOutput(io.MultiWriter(os.Stdout, file))

    // 设置 JSON 格式
    logger.SetFormatter(&logrus.JSONFormatter{
        TimestampFormat: "2006-01-02T15:04:05Z",
    })

    return nil
}

func main() {
    SetupFileLogger()
    logger.Info("应用启动")
}

使用 lumberjack 轮转

安装依赖

Bash
go get gopkg.in/natefinch/lumberjack.v2

按大小轮转

Go
import (
    "gopkg.in/natefinch/lumberjack.v2"
)

func SetupRotatingLogger() {
    logger.SetOutput(&lumberjack.Logger{
        Filename:   "logs/app.log",   // 文件名
        MaxSize:    100,              // 单文件最大 MB
        MaxBackups: 5,                // 保留旧文件数量
        MaxAge:     30,               // 保留天数
        Compress:   true,             // 压缩旧文件
        LocalTime:  true,             // 使用本地时间
    })

    logger.SetFormatter(&logrus.JSONFormatter{})
    logger.SetLevel(logrus.InfoLevel)
}

// 配置说明:
// - 单文件超过 100MB 自动轮转
// - 保留最多 5 个备份文件
// - 备份文件超过 30 天自动删除
// - 旧文件自动 gzip 压缩

多文件配置

Go
type MultiFileLogger struct {
    infoLogger  *lumberjack.Logger
    errorLogger *lumberjack.Logger
}

func SetupMultiFileLogger() *MultiFileLogger {
    // Info 日志文件
    infoLogger := &lumberjack.Logger{
        Filename:   "logs/info.log",
        MaxSize:    50,
        MaxBackups: 3,
        MaxAge:     7,
        Compress:   true,
    }

    // Error 日志文件
    errorLogger := &lumberjack.Logger{
        Filename:   "logs/error.log",
        MaxSize:    20,
        MaxBackups: 10,
        MaxAge:     30,
        Compress:   true,
    }

    return &MultiFileLogger{
        infoLogger:  infoLogger,
        errorLogger: errorLogger,
    }
}

// 使用 Hook 分流
type LevelFileHook struct {
    infoWriter  io.Writer
    errorWriter io.Writer
}

func (hook *LevelFileHook) Fire(entry *logrus.Entry) error {
    var writer io.Writer

    if entry.Level <= logrus.ErrorLevel {
        writer = hook.errorWriter
    } else {
        writer = hook.infoWriter
    }

    if writer != nil {
        msg, _ := entry.String()
        writer.Write([]byte(msg))
    }

    return nil
}

func (hook *LevelFileHook) Levels() []logrus.Level {
    return logrus.AllLevels
}

func main() {
    multiLogger := SetupMultiFileLogger()

    logger.AddHook(&LevelFileHook{
        infoWriter:  multiLogger.infoLogger,
        errorWriter: multiLogger.errorLogger,
    })

    // 控制台也输出
    logger.SetOutput(os.Stdout)

    logger.Info("正常日志")  // 写入 info.log
    logger.Error("错误日志") // 写入 info.log + error.log
}

按时间轮转

按天轮转

Go
type DailyRotatingLogger struct {
    basePath    string
    currentFile *os.File
    currentDay  string
}

func NewDailyRotatingLogger(basePath string) (*DailyRotatingLogger, error) {
    os.MkdirAll(basePath, 0755)

    rl := &DailyRotatingLogger{basePath: basePath}
    if err := rl.rotate(); err != nil {
        return nil, err
    }

    // 每天检查轮转
    go rl.dailyCheck()

    return rl, nil
}

func (rl *DailyRotatingLogger) rotate() error {
    today := time.Now().Format("2006-01-02")

    if rl.currentDay == today {
        return nil
    }

    // 关闭旧文件
    if rl.currentFile != nil {
        rl.currentFile.Close()
    }

    // 创建新文件
    filename := filepath.Join(rl.basePath, today+".log")
    file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
    if err != nil {
        return err
    }

    rl.currentFile = file
    rl.currentDay = today

    return nil
}

func (rl *DailyRotatingLogger) Write(p []byte) (n int, err error) {
    if err := rl.rotate(); err != nil {
        return 0, err
    }
    return rl.currentFile.Write(p)
}

func (rl *DailyRotatingLogger) dailyCheck() {
    for {
        time.Sleep(time.Hour)
        rl.rotate()

        // 清理超过 30 天的日志
        rl.cleanup()
    }
}

func (rl *DailyRotatingLogger) cleanup() {
    cutoff := time.Now().AddDate(0, 0, -30)

    files, _ := os.ReadDir(rl.basePath)
    for _, f := range files {
        dateStr := strings.TrimSuffix(f.Name(), ".log")
        date, err := time.Parse("2006-01-02", dateStr)
        if err == nil && date.Before(cutoff) {
            os.Remove(filepath.Join(rl.basePath, f.Name()))
        }
    }
}

Gin 日志中间件集成

请求日志文件

Go
func SetupRequestLogger() *lumberjack.Logger {
    return &lumberjack.Logger{
        Filename:   "logs/request.log",
        MaxSize:    100,
        MaxBackups: 5,
        MaxAge:     7,
        Compress:   true,
    }
}

func RequestLogMiddleware(fileLogger *lumberjack.Logger) gin.HandlerFunc {
    fileWriter := fileLogger

    return func(c *gin.Context) {
        startTime := time.Now()

        c.Next()

        latency := time.Since(startTime)

        // 写入文件
        logEntry := fmt.Sprintf("[%s] %s %s %d %dms %s\n",
            startTime.Format("2006-01-02 15:04:05"),
            c.Request.Method,
            c.Request.URL.Path,
            c.Writer.Status(),
            latency.Milliseconds(),
            c.ClientIP(),
        )

        fileWriter.Write([]byte(logEntry))
    }
}

func main() {
    r := gin.New()

    requestLogger := SetupRequestLogger()
    r.Use(RequestLogMiddleware(requestLogger))

    r.Run(":8080")
}

配置化日志轮转

Go
type LogRotationConfig struct {
    Filename   string `json:"filename"`
    MaxSize    int    `json:"max_size"`     // MB
    MaxBackups int    `json:"max_backups"`
    MaxAge     int    `json:"max_age"`      // days
    Compress   bool   `json:"compress"`
}

func LoadLogConfig(path string) (*LogRotationConfig, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, err
    }

    var config LogRotationConfig
    json.Unmarshal(data, &config)
    return &config, nil
}

func SetupLoggerFromConfig(config *LogRotationConfig) {
    logger.SetOutput(&lumberjack.Logger{
        Filename:   config.Filename,
        MaxSize:    config.MaxSize,
        MaxBackups: config.MaxBackups,
        MaxAge:     config.MaxAge,
        Compress:   config.Compress,
    })
}

// 配置文件示例 (log_config.json)
// {
//   "filename": "logs/app.log",
//   "max_size": 100,
//   "max_backups": 5,
//   "max_age": 30,
//   "compress": true
// }

日志文件权限

Go
func SecureLogFile(path string, perm os.FileMode) error {
    // 设置文件权限(仅所有者可读写)
    return os.Chmod(path, perm)
}

func SetupSecureLogger() {
    logger.SetOutput(&lumberjack.Logger{
        Filename: "logs/app.log",
        MaxSize:  100,
    })

    // 设置安全权限
    os.Chmod("logs/app.log", 0600)
    os.Chmod("logs", 0700)
}

轮转策略对比

策略适用场景优点缺点
按大小高流量应用文件大小可控时间分布不均
按时间定时分析时间维度清晰可能过大
按大小+时间生产环境双重控制配置复杂

注意:生产环境建议按大小轮转 + 压缩,便于日志分析。

要点总结

  1. 文件输出:使用 os.OpenFilelumberjack.Logger
  2. 按大小轮转:MaxSize 设置单文件最大大小
  3. 按时间轮转:自定义实现每日新文件
  4. 压缩清理:Compress 压缩旧文件,MaxAge 设置保留天数
  5. 多文件分流:不同级别日志写入不同文件
  6. 权限安全:设置合理的文件权限防止泄露

📝 发现内容有误?点击此处直接编辑

← 上一篇 Gin 日志分级与格式化
下一篇 → Gin 日志链路追踪与请求ID
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库