uniugm/admin/lib/xlog/handler/handler_file_rotate_daymax.go

203 lines
4.6 KiB
Go
Raw Normal View History

2025-04-18 17:17:23 +08:00
package handler
import (
"fmt"
"os"
"time"
"admin/lib/xlog/handler/logsyscall"
)
type RotatingDayMaxFileHandler struct {
baseName string
outPath string
fd *os.File
rotateInfo struct {
maxBytes int // 单个日志文件最大长度
maxBackupCount int // 最大归档文件数量
day time.Time // 记录跨天
}
}
// NewRotatingDayMaxFileHandler 每天00点滚动当超过大小也滚动
func NewRotatingDayMaxFileHandler(outPath, baseName string, maxBytes int, backupCount int) (*RotatingDayMaxFileHandler, error) {
if outPath == "" {
outPath = "log/"
}
h := new(RotatingDayMaxFileHandler)
h.baseName = baseName
h.outPath = outPath
h.rotateInfo.day = time.Now()
h.rotateInfo.maxBytes = maxBytes
h.rotateInfo.maxBackupCount = backupCount
fd, err := openFile(h.outPath, h.baseName, false)
if err != nil {
panic(err)
} else {
h.fd = fd
}
go h.rotateHandler()
return h, nil
}
func (h *RotatingDayMaxFileHandler) Write(p []byte) (n int, err error) {
return h.fd.Write(p)
}
func (h *RotatingDayMaxFileHandler) Close() error {
if h.fd != nil {
return h.fd.Close()
}
return nil
}
func (h *RotatingDayMaxFileHandler) rotateHandler() {
for {
time.Sleep(time.Second)
h.tryRotate()
}
}
func (h *RotatingDayMaxFileHandler) tryRotate() {
// 校验时间是否触发归档
now := time.Now()
if now.Day() != h.rotateInfo.day.Day() {
h.rotateDay()
h.rotateInfo.day = now
return
}
// 校验文件大小是否触发归档
size, err := calcFileSize(h.fd)
if err != nil {
outErrorLog("stat xlog file(%v) error:%v/home/likun/work/idlerpg-server/internal/utils/xlog", h.baseName, err)
return
}
if h.rotateInfo.maxBytes > 0 && h.rotateInfo.maxBytes <= size {
h.rotateSize()
return
}
}
// rotateAt 到点触发归档
func (h *RotatingDayMaxFileHandler) rotateDay() {
// 创建新的一天的日志文件
newFd, err := openFile(h.outPath, h.baseName, false)
if err != nil {
outErrorLog("new day rotate file, but open new file(%v) error:%v", h.baseName, err)
return
}
// 用新的一天的日志文件描述符接管当前使用的
oldFd := h.fd
h.fd = newFd
oldFd.Close()
}
// rotateSize 文件过大触发归档
func (h *RotatingDayMaxFileHandler) rotateSize() {
// 锁定文件,使触发归档的别的进程也锁住
lockFile(h.fd)
// 重新打开文件判断大小,防止文件被别的归档进程改名
curSize := calcFileNameSize(h.fd.Name())
if curSize < h.rotateInfo.maxBytes {
// 别的进程归档过了
unlockFile(h.fd)
return
}
// 滚动copy归档的文件那么归档的1号文件空出来了
baseFileName := h.fd.Name()
for i := h.rotateInfo.maxBackupCount; i > 0; i-- {
sfn := fmt.Sprintf("%s.%d", baseFileName, i)
dfn := fmt.Sprintf("%s.%d", baseFileName, i+1)
os.Rename(sfn, dfn)
}
// 将当前文件内容拷贝到归档1号文件
dfn := fmt.Sprintf("%s.1", baseFileName)
os.Rename(baseFileName, dfn)
// 重新创建当前日志文件得到新的描述符
newFd, err := os.OpenFile(baseFileName, os.O_CREATE|os.O_APPEND|os.O_RDWR|os.O_TRUNC, 0777)
if err != nil {
outErrorLog("rotate xlog file size, open file(%v) error:%v", baseFileName, err)
unlockFile(h.fd)
} else {
oldFd := h.fd
h.fd = newFd
unlockFile(h.fd)
oldFd.Close()
}
}
func calcFileSize(fd *os.File) (int, error) {
st, err := fd.Stat()
return int(st.Size()), err
}
func calcFileNameSize(fileName string) int {
fd, err := os.OpenFile(fileName, os.O_RDWR|os.O_APPEND, 0777)
if err != nil {
return 0
}
defer fd.Close()
st, err := fd.Stat()
if err != nil {
return 0
}
return int(st.Size())
}
var fileTimeStampFormat = "20060102"
func openFile(path, baseName string, isTrunc bool) (*os.File, error) {
os.MkdirAll(path, 0777)
timeNow := time.Now()
t := timeNow.Format(fileTimeStampFormat)
fullFileName := baseName + "-" + t + ".log"
if path != "" {
if path[len(path)-1] != '/' {
path += "/"
}
}
fullPathFileName := path + fullFileName
var fd *os.File
var err error
flag := os.O_CREATE | os.O_APPEND | os.O_RDWR
if isTrunc {
flag |= os.O_TRUNC
}
fd, err = os.OpenFile(fullPathFileName, flag, 0777)
if err != nil {
return fd, fmt.Errorf("open xlog file(%v) error:%v", fullPathFileName, err)
}
return fd, nil
}
func lockFile(fd *os.File) {
err := logsyscall.Flock(int(fd.Fd()), logsyscall.LOCK_EX)
if err != nil {
outErrorLog("lock file(%v) error:%v", fd.Name(), err)
}
}
func unlockFile(fd *os.File) {
err := logsyscall.Flock(int(fd.Fd()), logsyscall.LOCK_UN)
if err != nil {
outErrorLog("unlock file(%v) error:%v", fd.Name(), err)
}
}
func outErrorLog(format string, values ...interface{}) {
fmt.Fprintf(os.Stderr, format+"\n", values...)
}