103 lines
3.1 KiB
Go
103 lines
3.1 KiB
Go
package loggerx
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"io"
|
||
"sync"
|
||
"sync/atomic"
|
||
)
|
||
|
||
// 包级默认实例
|
||
//
|
||
// 两个刻意的设计:
|
||
// 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]
|
||
)
|
||
|
||
// 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(&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) {
|
||
return loggercDefault().Channel(ch)
|
||
}
|
||
|
||
func Info(ctx context.Context, v ...any) {
|
||
loggercDefault().logger(ctx, "info", v...)
|
||
}
|
||
|
||
func Infof(ctx context.Context, format string, v ...any) {
|
||
loggercDefault().logger(ctx, "info", fmt.Sprintf(format, v...))
|
||
}
|
||
|
||
func Error(ctx context.Context, v ...any) {
|
||
loggercDefault().logger(ctx, "error", v...)
|
||
}
|
||
|
||
func Errorf(ctx context.Context, format string, v ...any) {
|
||
loggercDefault().logger(ctx, "error", fmt.Sprintf(format, v...))
|
||
}
|