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...)) }