fix logger and redeem admin review findings
Some checks failed
CI / test (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
Security Scan / backend-security (push) Has been cancelled
Security Scan / frontend-security (push) Has been cancelled

This commit is contained in:
2026-04-20 11:24:36 +08:00
parent 3a0ca7f57f
commit ed642e8769
22 changed files with 563 additions and 63 deletions

View File

@@ -48,6 +48,7 @@ var (
atomicLevel zap.AtomicLevel
initOptions InitOptions
currentSink atomic.Value // sinkState
currentClose func()
stdLogUndo func()
bootstrapOnce sync.Once
)
@@ -72,16 +73,18 @@ func Init(options InitOptions) error {
func initLocked(options InitOptions) error {
normalized := options.normalized()
zl, al, err := buildLogger(normalized)
zl, al, closeFn, err := buildLogger(normalized)
if err != nil {
return err
}
prev := global.Load()
prevClose := currentClose
global.Store(zl)
sugar.Store(zl.Sugar())
atomicLevel = al
initOptions = normalized
currentClose = closeFn
bridgeSlogLocked()
bridgeStdLogLocked()
@@ -89,6 +92,9 @@ func initLocked(options InitOptions) error {
if prev != nil {
_ = prev.Sync()
}
if prevClose != nil {
prevClose()
}
return nil
}
@@ -205,6 +211,27 @@ func Sync() {
}
}
func Shutdown() {
mu.Lock()
defer mu.Unlock()
if stdLogUndo != nil {
stdLogUndo()
stdLogUndo = nil
}
if l := global.Load(); l != nil {
_ = l.Sync()
}
if currentClose != nil {
currentClose()
currentClose = nil
}
global.Store(nil)
sugar.Store(nil)
}
func bridgeStdLogLocked() {
if stdLogUndo != nil {
stdLogUndo()
@@ -238,7 +265,7 @@ func bridgeSlogLocked() {
slog.SetDefault(slog.New(newSlogZapHandler(base.Named("slog"))))
}
func buildLogger(options InitOptions) (*zap.Logger, zap.AtomicLevel, error) {
func buildLogger(options InitOptions) (*zap.Logger, zap.AtomicLevel, func(), error) {
level, _ := parseLevel(options.Level)
atomic := zap.NewAtomicLevelAt(level)
@@ -265,6 +292,7 @@ func buildLogger(options InitOptions) (*zap.Logger, zap.AtomicLevel, error) {
sinkCore := newSinkCore()
cores := make([]zapcore.Core, 0, 3)
var closers []io.Closer
if options.Output.ToStdout {
infoPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
@@ -273,12 +301,12 @@ func buildLogger(options InitOptions) (*zap.Logger, zap.AtomicLevel, error) {
errPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= atomic.Level() && lvl >= zapcore.WarnLevel
})
cores = append(cores, zapcore.NewCore(enc, zapcore.Lock(os.Stdout), infoPriority))
cores = append(cores, zapcore.NewCore(enc, zapcore.Lock(os.Stderr), errPriority))
cores = append(cores, zapcore.NewCore(enc, stdStreamWriteSyncer(os.Stdout), infoPriority))
cores = append(cores, zapcore.NewCore(enc, stdStreamWriteSyncer(os.Stderr), errPriority))
}
if options.Output.ToFile {
fileCore, filePath, fileErr := buildFileCore(enc, atomic, options)
fileCore, filePath, fileCloser, fileErr := buildFileCore(enc, atomic, options)
if fileErr != nil {
_, _ = fmt.Fprintf(os.Stderr, "time=%s level=WARN msg=\"日志文件输出初始化失败,降级为仅标准输出\" path=%s err=%v\n",
time.Now().Format(time.RFC3339Nano),
@@ -287,11 +315,12 @@ func buildLogger(options InitOptions) (*zap.Logger, zap.AtomicLevel, error) {
)
} else {
cores = append(cores, fileCore)
closers = append(closers, fileCloser)
}
}
if len(cores) == 0 {
cores = append(cores, zapcore.NewCore(enc, zapcore.Lock(os.Stdout), atomic))
cores = append(cores, zapcore.NewCore(enc, stdStreamWriteSyncer(os.Stdout), atomic))
}
core := zapcore.NewTee(cores...)
@@ -313,10 +342,14 @@ func buildLogger(options InitOptions) (*zap.Logger, zap.AtomicLevel, error) {
zap.String("service", options.ServiceName),
zap.String("env", options.Environment),
)
return logger, atomic, nil
return logger, atomic, func() {
for _, closer := range closers {
_ = closer.Close()
}
}, nil
}
func buildFileCore(enc zapcore.Encoder, atomic zap.AtomicLevel, options InitOptions) (zapcore.Core, string, error) {
func buildFileCore(enc zapcore.Encoder, atomic zap.AtomicLevel, options InitOptions) (zapcore.Core, string, io.Closer, error) {
filePath := options.Output.FilePath
if strings.TrimSpace(filePath) == "" {
filePath = resolveLogFilePath("")
@@ -324,7 +357,7 @@ func buildFileCore(enc zapcore.Encoder, atomic zap.AtomicLevel, options InitOpti
dir := filepath.Dir(filePath)
if err := os.MkdirAll(dir, 0o755); err != nil {
return nil, filePath, err
return nil, filePath, nil, err
}
lj := &lumberjack.Logger{
Filename: filePath,
@@ -334,7 +367,25 @@ func buildFileCore(enc zapcore.Encoder, atomic zap.AtomicLevel, options InitOpti
Compress: options.Rotation.Compress,
LocalTime: options.Rotation.LocalTime,
}
return zapcore.NewCore(enc, zapcore.AddSync(lj), atomic), filePath, nil
return zapcore.NewCore(enc, zapcore.AddSync(lj), atomic), filePath, lj, nil
}
type stdStreamSyncer struct {
file *os.File
}
func stdStreamWriteSyncer(file *os.File) zapcore.WriteSyncer {
return zapcore.Lock(&stdStreamSyncer{file: file})
}
func (s *stdStreamSyncer) Write(p []byte) (int, error) {
return s.file.Write(p)
}
func (s *stdStreamSyncer) Sync() error {
// Standard streams do not need fsync semantics, and on Windows a pipe-backed
// stdout/stderr can block indefinitely in FlushFileBuffers.
return nil
}
type sinkCore struct {

View File

@@ -33,6 +33,7 @@ func TestInit_DualOutput(t *testing.T) {
_ = stdoutW.Close()
_ = stderrW.Close()
})
t.Cleanup(Shutdown)
err = Init(InitOptions{
Level: "debug",
@@ -103,6 +104,7 @@ func TestInit_FileOutputFailureDowngrade(t *testing.T) {
_ = stderrR.Close()
_ = stderrW.Close()
})
t.Cleanup(Shutdown)
err = Init(InitOptions{
Level: "info",
@@ -149,6 +151,7 @@ func TestInit_CallerShouldPointToCallsite(t *testing.T) {
_ = stdoutW.Close()
_ = stderrW.Close()
})
t.Cleanup(Shutdown)
if err := Init(InitOptions{
Level: "info",

View File

@@ -95,7 +95,7 @@ func TestBuildFileCore_InvalidPathFallback(t *testing.T) {
EncodeLevel: zapcore.CapitalLevelEncoder,
}
encoder := zapcore.NewJSONEncoder(encoderCfg)
_, _, err := buildFileCore(encoder, zap.NewAtomicLevel(), opts)
_, _, _, err := buildFileCore(encoder, zap.NewAtomicLevel(), opts)
if err == nil {
t.Fatalf("buildFileCore() expected error for invalid path")
}

View File

@@ -59,6 +59,7 @@ func TestStdLogBridgeRoutesLevels(t *testing.T) {
_ = stderrR.Close()
_ = stderrW.Close()
})
t.Cleanup(Shutdown)
if err := Init(InitOptions{
Level: "debug",
@@ -121,6 +122,7 @@ func TestLegacyPrintfRoutesLevels(t *testing.T) {
_ = stderrR.Close()
_ = stderrW.Close()
})
t.Cleanup(Shutdown)
if err := Init(InitOptions{
Level: "debug",