203 lines
4.6 KiB
Go
203 lines
4.6 KiB
Go
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...)
|
||
}
|