更新
This commit is contained in:
+73
-13
@@ -3,40 +3,100 @@ package loggerx
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var loggerc *Logger
|
||||
// 包级默认实例
|
||||
//
|
||||
// 两个刻意的设计:
|
||||
// 1. 惰性初始化:import 一个日志库不该产生副作用(旧实现在 init 里 NewLogger,
|
||||
// 等于仅仅 import 就劫持全局 stdlib log、还起了几个后台 goroutine)
|
||||
// 2. 指针原子发布:NewLoggerc 不做「就地改字段」,而是按现有配置建一个新实例
|
||||
// 再整体换上。就地改会让并发打日志的读方看到半改状态,
|
||||
// expandData 这种 map 更会直接触发 fatal error: concurrent map read and map write
|
||||
var (
|
||||
loggercOnce sync.Once
|
||||
loggercPtr atomic.Pointer[Logger]
|
||||
)
|
||||
|
||||
func init() {
|
||||
loggerc = NewLogger(context.Background())
|
||||
// loggercDefault 返回包级实例,必要时创建
|
||||
func loggercDefault() *Logger {
|
||||
loggercOnce.Do(func() {
|
||||
loggercPtr.Store(NewLogger(context.Background(), SetAsGlobalLog()))
|
||||
})
|
||||
if l := loggercPtr.Load(); l != nil {
|
||||
return l
|
||||
}
|
||||
// CloseLoggerc 之后又有人用包级函数:重新建一个默认实例
|
||||
l := NewLogger(context.Background(), SetAsGlobalLog())
|
||||
loggercPtr.Store(l)
|
||||
return l
|
||||
}
|
||||
|
||||
// NewLoggerc 配置包级实例
|
||||
// 在现有配置基础上追加选项,构造新实例后原子替换,因此可与并发日志调用共存
|
||||
// (替换瞬间可能有极少几条日志落进旧实例)
|
||||
func NewLoggerc(ctx context.Context, opts ...Option) {
|
||||
base := loggercDefault()
|
||||
|
||||
// 复制一份选项再改:绝不动正在被并发读取的那一份
|
||||
opt := base.option
|
||||
opt.drivers = append([]io.Writer(nil), base.option.drivers...)
|
||||
opt.expandData = make(map[string]string, len(base.option.expandData))
|
||||
for k, v := range base.option.expandData {
|
||||
opt.expandData[k] = v
|
||||
}
|
||||
for _, apply := range opts {
|
||||
apply(&loggerc.option)
|
||||
apply(&opt)
|
||||
}
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
old := loggercPtr.Load()
|
||||
next := NewLogger(ctx, func(o *loggerOption) { *o = opt })
|
||||
// NewLogger 只在实例创建时决定要不要起刷盘协程,走的是入参选项,
|
||||
// 这里已经通过闭包把完整 opt 传进去了,无需额外处理
|
||||
loggercPtr.Store(next)
|
||||
|
||||
// 旧实例必须收掉,否则后台 goroutine 与文件句柄会一直累积
|
||||
if old != nil && old != next {
|
||||
_ = old.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// CloseLoggerc 关闭包级实例(并还原全局 stdlib log)
|
||||
// 建议在 main 退出前调用,否则包级实例缓冲里的日志(最多 32KB)不会落盘
|
||||
// 之后再次调用包级函数会自动新建一个默认实例,不会把包级 API 永久废掉
|
||||
func CloseLoggerc() error {
|
||||
l := loggercPtr.Load()
|
||||
if l == nil {
|
||||
return nil
|
||||
}
|
||||
err := l.Close()
|
||||
loggercPtr.Store(nil)
|
||||
loggercOnce = sync.Once{}
|
||||
return err
|
||||
}
|
||||
|
||||
func Channel(ch string) (r *Logger) {
|
||||
rr := *loggerc
|
||||
rr.channel = ch
|
||||
return &rr
|
||||
return loggercDefault().Channel(ch)
|
||||
}
|
||||
|
||||
func Info(ctx context.Context, v ...any) {
|
||||
loggerc.logger(ctx, "info", v...)
|
||||
loggercDefault().logger(ctx, "info", v...)
|
||||
}
|
||||
|
||||
func Infof(ctx context.Context, format string, v ...any) {
|
||||
s := fmt.Sprintf(format, v...)
|
||||
loggerc.logger(ctx, "info", s)
|
||||
loggercDefault().logger(ctx, "info", fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Error(ctx context.Context, v ...any) {
|
||||
loggerc.logger(ctx, "error", v...)
|
||||
loggercDefault().logger(ctx, "error", v...)
|
||||
}
|
||||
|
||||
func Errorf(ctx context.Context, format string, v ...any) {
|
||||
s := fmt.Sprintf(format, v...)
|
||||
loggerc.logger(ctx, "error", s)
|
||||
loggercDefault().logger(ctx, "error", fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user