From 249b38e156331ae182b38e27cce3c61a60dfbc7a Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 27 Sep 2026 15:32:20 +0200 Subject: [PATCH] fix(logger): reuse the open log rotator when InitLogger runs again Every InitLogger call built a new lumberjack rotator and dropped the old one without closing it, leaking a handle on 3xui.log per call, and loggers still writing through an old rotator kept it alive. On Windows the open handles block deleting the file, so TestInitLoggerConcurrentWithLogging failed its t.TempDir cleanup there. InitLogger now reuses the open rotator for the same path and closes it only when the path changes, and the test closes the logger it opened. Neither half is enough alone: with only one of them the test stays red on Windows. --- internal/logger/logger.go | 25 +++++++++++++++++-------- internal/logger/logger_test.go | 1 + 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/internal/logger/logger.go b/internal/logger/logger.go index b2f5c6c53..4489fc0be 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -93,8 +93,22 @@ func initFileBackend() logging.Backend { return nil } - logPath := filepath.Join(logDir, logFileName) - rotate := &lumberjack.Logger{ + backend := logging.NewLogBackend(fileRotateFor(filepath.Join(logDir, logFileName)), "", 0) + return logging.NewBackendFormatter(backend, newFormatter(true)) +} + +// fileRotateFor reuses the open rotator for logPath: a re-init that swapped in a +// new one would leave the old one holding the file, and loggers still writing to it. +func fileRotateFor(logPath string) *lumberjack.Logger { + fileRotateMu.Lock() + defer fileRotateMu.Unlock() + if fileRotate != nil && fileRotate.Filename == logPath { + return fileRotate + } + if fileRotate != nil { + _ = fileRotate.Close() + } + fileRotate = &lumberjack.Logger{ Filename: logPath, MaxSize: maxLogFileMB, MaxBackups: maxLogBackups, @@ -102,12 +116,7 @@ func initFileBackend() logging.Backend { LocalTime: true, Compress: compressRotated, } - fileRotateMu.Lock() - fileRotate = rotate - fileRotateMu.Unlock() - - backend := logging.NewLogBackend(rotate, "", 0) - return logging.NewBackendFormatter(backend, newFormatter(true)) + return fileRotate } // newFormatter creates a log formatter with optional timestamp. diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go index b086cb800..869bfd5df 100644 --- a/internal/logger/logger_test.go +++ b/internal/logger/logger_test.go @@ -36,6 +36,7 @@ func TestGetLogs_ReturnsAtMostC(t *testing.T) { // logging — CI caught that as a data race between InitLogger and Warningf. func TestInitLoggerConcurrentWithLogging(t *testing.T) { t.Setenv("XUI_LOG_FOLDER", t.TempDir()) + t.Cleanup(CloseLogger) stop := make(chan struct{}) var logging sync.WaitGroup