3. 日志滚动

实际原理就是通过自定义 WriteSyncer 接口来自定义日志写入逻辑。

package main

import (
	"fmt"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"os"
	"path/filepath"
	"sync"
	"time"
)

type DailyWriteSyncer struct {
	mu          sync.Mutex
	currentTime string
	file        *os.File
	logDir      string
	baseName    string
	maxKeepTime int
}

// NewDailyWriteSyncer 创建按天分割的 WriteSyncer
func NewDailyWriteSyncer(logDir, baseName string, maxKeepTime int) *DailyWriteSyncer {
	return &DailyWriteSyncer{
		logDir:      logDir,
		baseName:    baseName,
		maxKeepTime: maxKeepTime,
	}
}

// getCurrentDay 获取当前日期(格式:2006-01-02)
func (d *DailyWriteSyncer) getCurrentDay() string {
	return time.Now().Format("2006-01-02")
}

// openFile 打开或创建当天的日志文件
func (d *DailyWriteSyncer) openFile() error {

	currentDay := d.getCurrentDay()
	logPath := filepath.Join(d.logDir, fmt.Sprintf("%s-%s.log", d.baseName, currentDay))

	if err := os.MkdirAll(d.logDir, 0755); err != nil {
		return fmt.Errorf("failed to create log directory: %v", err)
	}

	file, err := os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
	if err != nil {
		return fmt.Errorf("failed to open log file: %v", err)
	}

	if d.file != nil {
		d.file.Close()
	}

	d.file = file
	d.currentTime = currentDay

	if d.maxKeepTime > 0 {
		go d.cleanExpiredLogs()
	}

	return nil

}

// Write 实现 zapcore.WriteSyncer 接口
func (d *DailyWriteSyncer) Write(p []byte) (n int, err error) {
	d.mu.Lock()
	defer d.mu.Unlock()

	currentDay := d.getCurrentDay()
	if d.currentTime != currentDay || d.file == nil {
		if err := d.openFile(); err != nil {
			return 0, err
		}
	}
	return d.file.Write(p)

}

// Sync 实现 zapcore.WriteSyncer 接口
func (d *DailyWriteSyncer) Sync() error {
	d.mu.Lock()
	defer d.mu.Unlock()

	if d.file != nil {
		return d.file.Sync()
	}

	return nil
}

// cleanExpiredLogs 清理过期日志
func (d *DailyWriteSyncer) cleanExpiredLogs() error {
	d.mu.Lock()
	defer d.mu.Unlock()

	day := time.Now().AddDate(0, 0, -d.maxKeepTime)
	expireDay := day.Format("2006-01-02")

	files, _ := os.ReadDir(d.logDir)
	for _, file := range files {
		if file.IsDir() {
			continue
		}

		name := file.Name()
		if len(name) < len(d.baseName)+11 {
			continue
		}

		timePart := name[len(d.baseName)+1 : len(d.baseName)+11]

		if timePart < expireDay {
			os.Remove(filepath.Join(d.logDir, name))
		}
	}
	return nil
}

func main() {
	encoderCfg := zapcore.EncoderConfig{
		MessageKey:  "message",
		LevelKey:    "level",
		TimeKey:     "time",
		EncodeTime:  zapcore.ISO8601TimeEncoder,
		EncodeLevel: zapcore.LowercaseLevelEncoder,
	}
	autoLevel := zap.NewAtomicLevel()

	jsonEncoder := zapcore.NewJSONEncoder(encoderCfg)
	dailyWs := NewDailyWriteSyncer("./logs", "app", 1)
	core := zapcore.NewCore(jsonEncoder, zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(dailyWs)), autoLevel)

	logger := zap.New(core, zap.AddCaller())
	defer logger.Sync()

	i := 0
	for {
		logger.Info("This is a log message", zap.Int("count", i))
		time.Sleep(3 * time.Second)
		i++
		if i == 20 {
			autoLevel.SetLevel(zap.ErrorLevel)
		}
	}
}

最后更新于