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)
}
轮转策略对比
| 策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 按大小 | 高流量应用 | 文件大小可控 | 时间分布不均 |
| 按时间 | 定时分析 | 时间维度清晰 | 可能过大 |
| 按大小+时间 | 生产环境 | 双重控制 | 配置复杂 |
注意:生产环境建议按大小轮转 + 压缩,便于日志分析。
要点总结
- 文件输出:使用
os.OpenFile或lumberjack.Logger - 按大小轮转:MaxSize 设置单文件最大大小
- 按时间轮转:自定义实现每日新文件
- 压缩清理:Compress 压缩旧文件,MaxAge 设置保留天数
- 多文件分流:不同级别日志写入不同文件
- 权限安全:设置合理的文件权限防止泄露
📝 发现内容有误?点击此处直接编辑