1 Star 0 Fork 0

simplexyz / simplelog-go

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
file_writer.go 6.55 KB
一键复制 编辑 原始数据 按行查看 历史
package log
import (
"context"
"fmt"
"log"
"os"
"path"
"path/filepath"
"strings"
"sync"
"time"
sutil "gitee.com/simplexyz/simpleutil-go"
sworker "gitee.com/simplexyz/simpleworker-go"
)
type file struct {
*os.File
w *fileWriter
level Level
path string
size int64
lastRotateTime time.Time
buffer []byte
}
func createFile(writer *fileWriter, level Level) (*file, error) {
f := &file{
w: writer,
level: level,
lastRotateTime: time.Now(),
}
if f.level.SubName() != "" {
f.path = filepath.Join(f.w.Dir, f.w.Name+"."+f.level.SubName()+".log")
} else {
f.path = filepath.Join(f.w.Dir, f.w.Name+".log")
}
var err error
f.File, err = os.OpenFile(f.path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return nil, err
}
info, err := os.Lstat(f.path)
if err != nil {
return nil, err
}
f.size = info.Size()
return f, nil
}
func (f *file) write(record IRecord) bool {
level := record.Level()
if f.level != LevelAll && f.level != level {
return false
}
f.buffer = append(f.buffer, record.Bytes()...)
if level >= LevelError {
f.sink()
return true
}
if len(f.buffer) >= 1024 {
f.sink()
return true
}
return false
}
func (f *file) sink() {
if len(f.buffer) == 0 {
return
}
n, _ := f.File.Write(f.buffer)
f.size += int64(n)
f.buffer = nil
}
func (f *file) rotate() {
_ = f.Sync()
_ = f.Close()
oldPath := f.path
newPath := f.path
nowTime := time.Now()
timestamp := nowTime.Format("2006-01-02-15-04-05")
if nowTime.Sub(f.lastRotateTime) < time.Second {
ms := nowTime.Format(".000")
ms = strings.Replace(ms, ".", "-", 1)
timestamp += ms
}
if f.level.SubName() != "" {
newPath = filepath.Join(f.w.Dir, fmt.Sprintf("%s.%s.%s.log", f.w.Name, f.level.SubName(), timestamp))
} else {
newPath = filepath.Join(f.w.Dir, fmt.Sprintf("%s.%s.log", f.w.Name, timestamp))
}
_ = os.Rename(oldPath, newPath)
f.File, _ = os.OpenFile(f.path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
f.size = 0
f.lastRotateTime = nowTime
}
type fileWriter struct {
*sworker.Worker
FileOption
stoppedWg sync.WaitGroup
files []*file
ticker *time.Ticker
lastCheckTime time.Time
queue chan IRecord
}
func CreateFileWriter(option *FileOption) (IWriter, error) {
if option == nil {
option = &FileOption{}
}
w := &fileWriter{
FileOption: *option,
lastCheckTime: time.Now(),
}
var err error
if w.Name == "" {
// 默认使用可执行文件名(不带扩展名)
w.Name, err = sutil.GetProgramFileBaseName()
if err != nil {
return nil, fmt.Errorf("init file writer name fail, %w", err)
}
}
if _, ok := GetFileWriter(w.Name); ok {
return nil, fmt.Errorf("file writer already exist")
}
if w.Dir == "" {
// 默认保存在当前目录
w.Dir = "."
}
w.Dir = filepath.Join(w.Dir, w.Name)
if w.MaxSize <= 0 {
// 默认单个文件最大30MB
w.MaxSize = 30 * 1024 * 1024
}
if w.MaxAge <= 0 {
// 默认保存30天
w.MaxAge = 30 * 24 * time.Hour
}
if w.SeperatedLevels <= 0 {
// 默认分离error日志
w.SeperatedLevels = LevelError
}
if w.QueueSize <= 0 {
w.QueueSize = 1024
}
if w.TickInterval <= 0 {
// 默认间隔3秒
w.TickInterval = 3 * time.Second
}
w.ticker = time.NewTicker(w.TickInterval)
err = sutil.MkDir(w.Dir)
if err != nil {
return nil, fmt.Errorf("create directory fail, %w", err)
}
f, e := createFile(w, LevelAll)
if e != nil {
return nil, fmt.Errorf("create file fail, path=[%s], %w", f.path, e)
}
w.files = append(w.files, f)
if w.SeperatedLevels > 0 {
EachLevel(func(level Level) (continued bool) {
if !level.IsIn(w.SeperatedLevels) {
return true
}
f, e = createFile(w, level)
if e != nil {
err = fmt.Errorf("open f fail, path=[%s], %w", f.path, e)
return false
}
w.files = append(w.files, f)
return true
})
if err != nil {
return nil, err
}
}
w.queue = make(chan IRecord, w.QueueSize)
w.Worker, err = sworker.Create(
nil, true,
sworker.WithOnWorking(w.onWorking),
sworker.WithOnStop(w.onStop),
sworker.WithStopWaiter(&w.stoppedWg),
)
if err != nil {
writers.Store(w.Name, w)
}
return w, err
}
func MustCreateFileWriter(option *FileOption) IWriter {
w, err := CreateFileWriter(option)
if err != nil {
panic(err)
}
return w
}
func (w *fileWriter) Write(record IRecord) {
if !w.Working() {
return
}
if record == nil {
return
}
w.queue <- record
}
func (w *fileWriter) write(record IRecord) {
for _, f := range w.files {
if f.write(record) {
w.tryRotate(f)
}
}
destroyRecord(record)
}
func (w *fileWriter) Destroy() {
w.Worker.Stop(func() {
close(w.queue)
}, true)
}
func (w *fileWriter) onWorking(ctx context.Context) bool {
for {
select {
case <-ctx.Done():
for r := range w.queue {
w.write(r)
}
return false
case <-w.ticker.C:
w.onTimeout()
case r, ok := <-w.queue:
if ok {
w.write(r)
}
}
}
}
func (w *fileWriter) onStop() {
w.ticker.Stop()
}
func (w *fileWriter) tryRotate(f *file) bool {
if f.size >= w.MaxSize {
f.rotate()
return true
}
if !sutil.IsSameDay(time.Now(), w.lastCheckTime) {
f.rotate()
return true
}
return false
}
func (w *fileWriter) onTimeout() {
//log.Println("onTimeout")
// 滚转
for _, f := range w.files {
f.sink()
w.tryRotate(f)
}
now := time.Now()
var needBackupFilePaths []string
// 将超时文件归档,然后删除
//log.Print("remove timeout file begin")
_ = sutil.Walk(w.Dir, "log", nil, nil, func(path string, info os.FileInfo, name string, isInBlackList bool) (continued bool) {
//log.Printf("path=%s", path)
for _, f := range w.files {
if info.Name() == sutil.GetFileName(f.path) {
//log.Printf("ignore file %s", path)
return true
}
}
if now.Sub(info.ModTime()) < w.MaxAge {
return true
}
needBackupFilePaths = append(needBackupFilePaths, path)
log.Printf("find time out file %s", path)
return true
})
if len(needBackupFilePaths) > 0 {
timestamp := now.Format("2006-01-02-15-04-05")
// 单独起一个协程执行来避免阻塞
//w.stoppedWg.Add(1)
//go func() {
backupFilePath := path.Join(w.Dir, fmt.Sprintf("%s.%s.log.zip", w.Name, timestamp))
backupFilePath, _ = filepath.Abs(backupFilePath)
if err := sutil.Zip(needBackupFilePaths, backupFilePath); err != nil {
log.Printf("backup log file to %s fail, %v", backupFilePath, err)
} else {
log.Printf("backup log file to %s success", backupFilePath)
}
for _, p := range needBackupFilePaths {
_ = os.Remove(p)
}
//w.stoppedWg.Done()
//}()
}
//log.Print("remove timeout file end")
w.lastCheckTime = now
}
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/simplexyz/simplelog-go.git
git@gitee.com:simplexyz/simplelog-go.git
simplexyz
simplelog-go
simplelog-go
master

搜索帮助