Files
loggerx/loggerc.go
T
2026-09-14 00:14:30 +08:00

103 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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...))
}