204 lines
4.6 KiB
Go
204 lines
4.6 KiB
Go
|
|
package impl_localfile
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"fmt"
|
|||
|
|
"log"
|
|||
|
|
"os"
|
|||
|
|
"path/filepath"
|
|||
|
|
"strconv"
|
|||
|
|
"sync"
|
|||
|
|
"time"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
con_FILE_SUFFIX = "txt"
|
|||
|
|
|
|||
|
|
defaultPanicNum = 10 // 默认支持的连续panic次数
|
|||
|
|
defaultPanicIntervals = 5 // 秒
|
|||
|
|
|
|||
|
|
fileMaxSize = 1 << 27 // 128M
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
type fileLog struct {
|
|||
|
|
logPath string
|
|||
|
|
|
|||
|
|
// fileDir + fileName 用于判断是否需要切换日志文件
|
|||
|
|
fileDir string
|
|||
|
|
fileName string
|
|||
|
|
lv levelType
|
|||
|
|
|
|||
|
|
msgChan chan logObject
|
|||
|
|
|
|||
|
|
f *os.File
|
|||
|
|
|
|||
|
|
// 如果出现未知panic并一直发生,那么应该要真正的panic
|
|||
|
|
panicNum int
|
|||
|
|
panicTime time.Time
|
|||
|
|
|
|||
|
|
// 当前已经写入的大小
|
|||
|
|
curWriteSize int64
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func newLog(logPath string, lv levelType) *fileLog {
|
|||
|
|
f := &fileLog{
|
|||
|
|
logPath: logPath,
|
|||
|
|
fileDir: "",
|
|||
|
|
fileName: "",
|
|||
|
|
msgChan: make(chan logObject, 10240),
|
|||
|
|
lv: lv,
|
|||
|
|
f: nil,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
go f.loop()
|
|||
|
|
|
|||
|
|
return f
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (f *fileLog) loop() {
|
|||
|
|
defer func() {
|
|||
|
|
if r := recover(); r != nil {
|
|||
|
|
// 将错误输出,而不是记录到文件,是因为可能导致死循环
|
|||
|
|
fmt.Println("log file lv:", f.lv, " err:", r)
|
|||
|
|
|
|||
|
|
// 超过间隔时间重置
|
|||
|
|
if time.Since(f.panicTime)/time.Second > defaultPanicIntervals {
|
|||
|
|
f.panicNum = 0
|
|||
|
|
f.panicTime = time.Now()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 这里处理连续panic 也是防止循环调用形成死循环
|
|||
|
|
f.panicNum++
|
|||
|
|
if f.panicNum >= defaultPanicNum {
|
|||
|
|
panic(r)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
go f.loop()
|
|||
|
|
}()
|
|||
|
|
|
|||
|
|
for logItem := range f.msgChan {
|
|||
|
|
f.writeLog(logItem)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (f *fileLog) writeLog(logObj logObject) {
|
|||
|
|
// 由于不使用直接 logUtil 调用,logUtilPlus 存在终端打印的控制,所以 logUtil 只作为纯文件日志组件
|
|||
|
|
// if PrintImportantLog && (logObj.level == warn || logObj.level == _error || logObj.level == fatal) {
|
|||
|
|
// fmt.Println(logObj.logInfo)
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
// 检查是否需要去切换文件或者创建文件
|
|||
|
|
f.checkFileAndLoop(logObj.level, logObj.ifIncludeHour)
|
|||
|
|
|
|||
|
|
f.curWriteSize += int64(len(logObj.logInfo))
|
|||
|
|
_, _ = f.f.WriteString(logObj.logInfo)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查文件是否需要切换
|
|||
|
|
func (f *fileLog) checkFileAndLoop(level levelType, ifIncludeHour bool) {
|
|||
|
|
// 获取当前时间
|
|||
|
|
now := time.Now()
|
|||
|
|
fileAbsoluteDirectory := filepath.Join(f.logPath, strconv.Itoa(now.Year()), strconv.Itoa(int(now.Month())))
|
|||
|
|
fileName := ""
|
|||
|
|
|
|||
|
|
if ifIncludeHour {
|
|||
|
|
fileName = fmt.Sprintf("%s.%s.%s", time.Now().Format("2006-01-02-15"), level, con_FILE_SUFFIX)
|
|||
|
|
} else {
|
|||
|
|
fileName = fmt.Sprintf("%s.%s.%s", time.Now().Format("2006-01-02"), level, con_FILE_SUFFIX)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 说明已经存在, 检查文件大小,并切换
|
|||
|
|
if f.f != nil && f.fileName == fileName && f.fileDir == fileAbsoluteDirectory {
|
|||
|
|
if f.curWriteSize < fileMaxSize {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 大小超额切换文件
|
|||
|
|
f.switchFile()
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建文件夹
|
|||
|
|
if f.fileDir != fileAbsoluteDirectory {
|
|||
|
|
if err := os.MkdirAll(fileAbsoluteDirectory, os.ModePerm|os.ModeTemporary); err != nil {
|
|||
|
|
log.Println("make dir all is err :", err)
|
|||
|
|
}
|
|||
|
|
f.fileDir = fileAbsoluteDirectory
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建文件
|
|||
|
|
if f.fileName != fileName {
|
|||
|
|
f.fileName = fileName
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 到这里说明要切换了,关闭之前的file
|
|||
|
|
if f.f != nil {
|
|||
|
|
_ = f.f.Close()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 正常切换文件
|
|||
|
|
f.createFile()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (f *fileLog) switchFile() {
|
|||
|
|
// 关闭文件
|
|||
|
|
_ = f.f.Close()
|
|||
|
|
|
|||
|
|
// 重命名
|
|||
|
|
curFileName := filepath.Join(f.fileDir, f.fileName)
|
|||
|
|
newFileName := filepath.Join(f.fileDir, fmt.Sprintf("%s.%s.%s", f.fileName, time.Now().Format("15-04-05.999"), con_FILE_SUFFIX))
|
|||
|
|
err := os.Rename(curFileName, newFileName)
|
|||
|
|
if err != nil {
|
|||
|
|
log.Println(err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 再次创建文件
|
|||
|
|
f.createFile()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (f *fileLog) createFile() {
|
|||
|
|
// 得到最终的文件绝对路径
|
|||
|
|
fileAbsolutePath := filepath.Join(f.fileDir, f.fileName)
|
|||
|
|
// 打开文件(如果文件存在就以读写模式打开,并追加写入;如果文件不存在就创建,然后以写模式打开。)
|
|||
|
|
file, err := os.OpenFile(fileAbsolutePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm|os.ModeTemporary)
|
|||
|
|
if err != nil {
|
|||
|
|
log.Println("open file is err :", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
f.f = file
|
|||
|
|
stat, _ := file.Stat()
|
|||
|
|
f.curWriteSize = stat.Size()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (f *fileLog) SetLogPath(logPath string) {
|
|||
|
|
f.logPath = logPath
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Close 最后等待 3s 钟
|
|||
|
|
func (f *fileLog) Close(wg *sync.WaitGroup, waitFinish bool) {
|
|||
|
|
ticker := time.NewTicker(3 * time.Second)
|
|||
|
|
defer func() {
|
|||
|
|
ticker.Stop()
|
|||
|
|
|
|||
|
|
if f.f != nil {
|
|||
|
|
_ = f.f.Close()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
wg.Done()
|
|||
|
|
}()
|
|||
|
|
|
|||
|
|
for {
|
|||
|
|
if !waitFinish || len(f.msgChan) == 0 {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
select {
|
|||
|
|
case <-ticker.C:
|
|||
|
|
fmt.Println("wait close log file timeout:3s")
|
|||
|
|
return
|
|||
|
|
default:
|
|||
|
|
// 1ms 写入文件500条,最多等待 15ms 检查一次就行
|
|||
|
|
time.Sleep(15 * time.Millisecond)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|