优化一些问题&修复一些BUG
This commit is contained in:
+82
-37
@@ -5,13 +5,11 @@ package loggerx
|
||||
// desc: 日志封装类
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
// "sync_log/global"
|
||||
@@ -22,16 +20,37 @@ import (
|
||||
// 需要实现io.Writer接口
|
||||
type Logger struct {
|
||||
ctx context.Context
|
||||
filePath *sync.Map // filePath
|
||||
mu *sync.Mutex
|
||||
filePath map[fileKey]*logFile // 每个 (channel,event) 一个句柄
|
||||
mu *sync.RWMutex // 保护 filePath
|
||||
writeMu *sync.Mutex // 串行化文件写入(bufio 不是并发安全的)
|
||||
option loggerOption
|
||||
channel string
|
||||
writeType writeType // 是否异步落盘,这里作用范围是本条,优先判断这里
|
||||
|
||||
closeOnce *sync.Once
|
||||
done chan struct{} // 通用关闭信号(delete / ctx 相关 goroutine 用)
|
||||
workerDone chan struct{} // 异步消费 goroutine 退出信号
|
||||
closing chan struct{} // 异步投递关闭信号:只关一次
|
||||
|
||||
// 异步队列相关的整块状态都挂在指针后面:Logger 会被 Channel()/WriteAsync()
|
||||
// 浅拷贝,如果状态直接放在 Logger 里,拷贝出的实例就会各看各的
|
||||
// (曾经因此让 Close 看不到真正的队列,白等一场、最后一条日志丢失)
|
||||
async *asyncCtl
|
||||
}
|
||||
|
||||
type filePath struct {
|
||||
file *os.File
|
||||
fileName string
|
||||
// asyncCtl 异步队列的共享控制块:所有拷贝共享同一份
|
||||
type asyncCtl struct {
|
||||
mu sync.Mutex
|
||||
ch chan cacheData
|
||||
closed bool // 只能读写于 mu 保护下
|
||||
}
|
||||
|
||||
// fileKey 文件缓存的键
|
||||
// 必须同时包含 channel 与 event:文件名由两者共同决定,只按 channel 缓存会
|
||||
// 导致 info/error 互相顶掉句柄,每次写入都 close+open 一次文件
|
||||
type fileKey struct {
|
||||
channel string
|
||||
event string
|
||||
}
|
||||
|
||||
func NewLogger(ctx context.Context, opts ...Option) *Logger {
|
||||
@@ -46,11 +65,16 @@ func NewLogger(ctx context.Context, opts ...Option) *Logger {
|
||||
}
|
||||
|
||||
l := &Logger{
|
||||
ctx: ctx,
|
||||
filePath: &sync.Map{},
|
||||
mu: &sync.Mutex{},
|
||||
option: opt,
|
||||
writeType: writeTypeDefault,
|
||||
ctx: ctx,
|
||||
filePath: make(map[fileKey]*logFile),
|
||||
mu: &sync.RWMutex{},
|
||||
writeMu: &sync.Mutex{},
|
||||
option: opt,
|
||||
writeType: writeTypeDefault,
|
||||
closeOnce: &sync.Once{},
|
||||
done: make(chan struct{}),
|
||||
workerDone: make(chan struct{}),
|
||||
async: &asyncCtl{},
|
||||
}
|
||||
|
||||
log.SetOutput(l)
|
||||
@@ -66,21 +90,54 @@ func NewLogger(ctx context.Context, opts ...Option) *Logger {
|
||||
go l.delete()
|
||||
|
||||
// 强制刷盘
|
||||
// 用 done 而不是只监听 ctx:ctx 为 Background 时 Close 也要能把 goroutine 收回去
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
l.MustSync()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
l.MustSync()
|
||||
case <-l.done:
|
||||
}
|
||||
}()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// 强制刷盘
|
||||
func (l *Logger) MustSync() {
|
||||
l.filePath.Range(func(key, value any) bool {
|
||||
f := value.(*filePath)
|
||||
f.file.Sync()
|
||||
return true
|
||||
// 关闭日志:关闭异步队列、落盘并关闭所有文件句柄
|
||||
// 重复调用安全,调用后本实例的后台 goroutine 会全部退出
|
||||
func (l *Logger) Close() error {
|
||||
var err error
|
||||
l.closeOnce.Do(func() {
|
||||
// 1. 停投递 + 等消费协程把队列里已入队的任务全部写完
|
||||
l.drainAsync()
|
||||
|
||||
// 2. 在写锁内完成最后一次刷盘与关闭
|
||||
// 此后不会再有写入(投递已关闭,同步写入也要先抢到这把锁)
|
||||
l.writeMu.Lock()
|
||||
syncErr := l.MustSync()
|
||||
closeErr := l.close()
|
||||
l.writeMu.Unlock()
|
||||
err = joinErrors([]error{syncErr, closeErr})
|
||||
|
||||
// 3. 最后再发通用关闭信号,让 delete / ctx 相关 goroutine 退出
|
||||
close(l.done)
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// 强制刷盘:只 flush 缓存数据,文件继续可写
|
||||
func (l *Logger) MustSync() error {
|
||||
l.mu.RLock()
|
||||
files := make([]*logFile, 0, len(l.filePath))
|
||||
for _, f := range l.filePath {
|
||||
files = append(files, f)
|
||||
}
|
||||
l.mu.RUnlock()
|
||||
|
||||
var errs []error
|
||||
for _, f := range files {
|
||||
errs = append(errs, f.Sync())
|
||||
}
|
||||
return joinErrors(errs)
|
||||
}
|
||||
|
||||
func (l *Logger) Channel(ch string) (r *Logger) {
|
||||
@@ -147,32 +204,20 @@ func (l *Logger) Warnf(ctx context.Context, format string, v ...any) {
|
||||
l.logger(ctx, "warn", s)
|
||||
}
|
||||
|
||||
// 添加固定的内容
|
||||
// func (l *Logger) ContextWithFields(ctx context.Context, v ...any) {
|
||||
// l.logger(ctx, "add", v...)
|
||||
// }
|
||||
// func (l *Logger) Field(key,val string) {
|
||||
// l.logger(nil, "add", key,val)
|
||||
// }
|
||||
|
||||
func getGID() string {
|
||||
b := make([]byte, 64)
|
||||
b = b[:runtime.Stack(b, false)]
|
||||
b = bytes.TrimPrefix(b, []byte("goroutine "))
|
||||
b = b[:bytes.IndexByte(b, ' ')]
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// 验证文件夹权限
|
||||
// 根文件夹如果不存在则创建
|
||||
func checkDir(dir string) bool {
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
log.Println("创建文件夹失败", err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
// 目标存在但不可访问(权限不足等),此时返回 false 才是这个函数的本意
|
||||
log.Println("日志文件夹不可用", dir, err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user