一、为什么需要日志轮转

在开发过程中,日志文件会不断增长。如果不加以控制,单个日志文件可能占用大量磁盘空间,导致系统性能下降甚至崩溃。想象一下,一个运行了半年的服务,日志文件已经涨到几十GB,这时候查找某个时间点的错误信息就像大海捞针。日志轮转就是为了解决这个问题——通过自动切割、压缩和清理历史日志,让日志管理变得轻松高效。

二、logrotate基础配置

logrotate是Linux系统自带的日志轮转工具,通过简单的配置就能实现自动化管理。下面是一个典型的Golang应用日志轮转配置示例(技术栈:Linux + logrotate):

# /etc/logrotate.d/golang_app  
/var/log/golang_app/*.log {  
    daily                      # 每天轮转一次  
    missingok                  # 如果日志不存在也不报错  
    rotate 7                   # 保留最近7天的日志  
    compress                   # 使用gzip压缩旧日志  
    delaycompress              # 延迟到下次轮转时压缩  
    notifempty                 # 空日志文件不轮转  
    create 0644 root root      # 新日志文件的权限和所有者  
    sharedscripts              # 所有日志处理完再执行脚本  
    postrotate  
        # 通知Golang应用重新打开日志文件(需应用支持)  
        kill -USR1 $(cat /var/run/golang_app.pid)  
    endscript  
}

关键参数解析:

  • daily/weekly/monthly:定义轮转频率
  • size 100M:按大小轮转(与时间条件互斥)
  • copytruncate:适用于无法重开日志的应用(可能有丢失数据风险)

三、Golang中的日志重载机制

要让logrotate生效,Golang程序需要处理USR1信号来重新打开日志文件。以下是实现示例(技术栈:Golang):

package main

import (
	"log"
	"os"
	"os/signal"
	"syscall"
)

var logFile *os.File

func setupLogger() {
	var err error
	logFile, err = os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Fatal(err)
	}
	log.SetOutput(logFile)
}

func main() {
	setupLogger()
	defer logFile.Close()

	// 监听USR1信号
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGUSR1)
	
	go func() {
		for {
			<-sigChan
			log.Println("收到日志重载信号")
			logFile.Close()
			setupLogger() // 重新初始化日志文件
		}
	}()

	// 模拟日志输出
	for {
		log.Println("这是一条模拟日志")
		time.Sleep(5 * time.Second)
	}
}

注意事项:

  1. 生产环境建议使用lumberjack等成熟库实现日志轮转
  2. 容器化部署时需要挂载持久化卷存储日志
  3. 多实例部署时避免日志文件冲突

四、高级场景与优化技巧

4.1 按日志大小轮转

当应用日志量波动较大时,可以改用大小触发轮转:

/var/log/golang_app/*.log {
    size 100M    # 超过100MB立即轮转
    rotate 10
    compress
}

4.2 日志压缩优化

默认的gzip压缩可能消耗CPU,对于高性能场景可调整:

compresscmd /usr/bin/pigz  # 使用并行压缩工具
compressext .gz
compressoptions "-9"       # 最高压缩比

4.3 容器环境下的日志处理

在Docker中建议采用边车模式:

# docker-compose.yml示例
version: '3'
services:
  app:
    image: golang_app
    volumes:
      - ./logs:/var/log/golang_app
  logrotate:
    image: blacklabelops/logrotate
    volumes:
      - ./logs:/logs
      - ./logrotate.conf:/etc/logrotate.d/app

五、常见问题排查

  1. 日志没有轮转

    • 检查cron服务是否运行:systemctl status cron
    • 手动测试配置:logrotate -vf /etc/logrotate.d/golang_app
  2. 权限问题

    • 确保应用有日志目录写入权限
    • SELinux环境下可能需要:chcon -R -t var_log_t /var/log/golang_app
  3. 磁盘空间不足

    • 通过df -h检查磁盘使用情况
    • 调整轮转策略保留更少历史日志

六、技术方案对比

方案 优点 缺点
logrotate 系统原生支持,资源消耗低 需要应用配合信号处理
lumberjack 内置轮转,无需外部依赖 仅适用于Golang生态
ELK方案 支持集中式日志分析 架构复杂,资源占用高

七、最佳实践总结

  1. 关键服务日志建议同时启用时间和大小轮转策略
  2. 保留日志时长根据存储容量和审计要求平衡
  3. 日志文件名建议包含日期信息(如app-20240501.log
  4. 定期检查轮转是否正常执行(可通过日志文件时间戳确认)

通过合理的日志轮转配置,既能保证历史日志可追溯,又能避免磁盘爆炸。就像定期整理衣柜一样,养成好的日志管理习惯,关键时刻才不会手忙脚乱。