mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-19 16:37:19 +00:00
fix(logger): fix data race in InitLogger
Replace the package logger variable with an atomic.Pointer so InitLogger swapping the handle no longer races with concurrent Debug/Info/Warning/Error calls from other goroutines. Also guard fileRotate with a mutex, and add a regression test that reproduces the race under concurrent logging.
This commit is contained in:
+32
-17
@@ -8,6 +8,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
"github.com/op/go-logging"
|
||||||
@@ -30,10 +31,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Initialized to a usable default so logging never nil-derefs before InitLogger
|
// InitLogger swaps the handle while other goroutines are logging, so it is
|
||||||
// runs — the "migrate" and "setting" CLI subcommands log without calling it.
|
// published atomically — a plain assignment is an unsafe publication.
|
||||||
logger = logging.MustGetLogger("x-ui")
|
logger atomic.Pointer[logging.Logger]
|
||||||
fileRotate *lumberjack.Logger // nil when file backend disabled
|
|
||||||
|
// fileRotateMu guards fileRotate against a concurrent InitLogger/CloseLogger.
|
||||||
|
fileRotateMu sync.Mutex
|
||||||
|
fileRotate *lumberjack.Logger // nil when file backend disabled
|
||||||
|
|
||||||
// logBuffer maintains recent log entries in memory for web UI retrieval;
|
// logBuffer maintains recent log entries in memory for web UI retrieval;
|
||||||
// logBufferMu guards it — written from many goroutines, read by the web UI.
|
// logBufferMu guards it — written from many goroutines, read by the web UI.
|
||||||
@@ -45,6 +49,12 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// A usable default so logging never nil-derefs before InitLogger runs — the
|
||||||
|
// "migrate" and "setting" CLI subcommands log without calling it.
|
||||||
|
func init() {
|
||||||
|
logger.Store(logging.MustGetLogger("x-ui"))
|
||||||
|
}
|
||||||
|
|
||||||
// InitLogger initializes dual logging backends: console/syslog and file.
|
// InitLogger initializes dual logging backends: console/syslog and file.
|
||||||
// Console logging uses the specified level, file logging always uses DEBUG level.
|
// Console logging uses the specified level, file logging always uses DEBUG level.
|
||||||
func InitLogger(level logging.Level) {
|
func InitLogger(level logging.Level) {
|
||||||
@@ -66,7 +76,7 @@ func InitLogger(level logging.Level) {
|
|||||||
|
|
||||||
multiBackend := logging.MultiLogger(backends...)
|
multiBackend := logging.MultiLogger(backends...)
|
||||||
newLogger.SetBackend(multiBackend)
|
newLogger.SetBackend(multiBackend)
|
||||||
logger = newLogger
|
logger.Store(newLogger)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initDefaultBackend creates the console/syslog logging backend.
|
// initDefaultBackend creates the console/syslog logging backend.
|
||||||
@@ -104,7 +114,7 @@ func initFileBackend() logging.Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logPath := filepath.Join(logDir, logFileName)
|
logPath := filepath.Join(logDir, logFileName)
|
||||||
fileRotate = &lumberjack.Logger{
|
rotate := &lumberjack.Logger{
|
||||||
Filename: logPath,
|
Filename: logPath,
|
||||||
MaxSize: maxLogFileMB,
|
MaxSize: maxLogFileMB,
|
||||||
MaxBackups: maxLogBackups,
|
MaxBackups: maxLogBackups,
|
||||||
@@ -112,8 +122,11 @@ func initFileBackend() logging.Backend {
|
|||||||
LocalTime: true,
|
LocalTime: true,
|
||||||
Compress: compressRotated,
|
Compress: compressRotated,
|
||||||
}
|
}
|
||||||
|
fileRotateMu.Lock()
|
||||||
|
fileRotate = rotate
|
||||||
|
fileRotateMu.Unlock()
|
||||||
|
|
||||||
backend := logging.NewLogBackend(fileRotate, "", 0)
|
backend := logging.NewLogBackend(rotate, "", 0)
|
||||||
return logging.NewBackendFormatter(backend, newFormatter(true))
|
return logging.NewBackendFormatter(backend, newFormatter(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +142,8 @@ func newFormatter(withTime bool) logging.Formatter {
|
|||||||
// CloseLogger closes the rotating log writer and cleans up resources.
|
// CloseLogger closes the rotating log writer and cleans up resources.
|
||||||
// Should be called during application shutdown.
|
// Should be called during application shutdown.
|
||||||
func CloseLogger() {
|
func CloseLogger() {
|
||||||
|
fileRotateMu.Lock()
|
||||||
|
defer fileRotateMu.Unlock()
|
||||||
if fileRotate != nil {
|
if fileRotate != nil {
|
||||||
_ = fileRotate.Close()
|
_ = fileRotate.Close()
|
||||||
fileRotate = nil
|
fileRotate = nil
|
||||||
@@ -137,61 +152,61 @@ func CloseLogger() {
|
|||||||
|
|
||||||
// Debug logs a debug message and adds it to the log buffer.
|
// Debug logs a debug message and adds it to the log buffer.
|
||||||
func Debug(args ...any) {
|
func Debug(args ...any) {
|
||||||
logger.Debug(args...)
|
logger.Load().Debug(args...)
|
||||||
addToBuffer("DEBUG", fmt.Sprint(args...))
|
addToBuffer("DEBUG", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debugf logs a formatted debug message and adds it to the log buffer.
|
// Debugf logs a formatted debug message and adds it to the log buffer.
|
||||||
func Debugf(format string, args ...any) {
|
func Debugf(format string, args ...any) {
|
||||||
logger.Debugf(format, args...)
|
logger.Load().Debugf(format, args...)
|
||||||
addToBuffer("DEBUG", fmt.Sprintf(format, args...))
|
addToBuffer("DEBUG", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info logs an info message and adds it to the log buffer.
|
// Info logs an info message and adds it to the log buffer.
|
||||||
func Info(args ...any) {
|
func Info(args ...any) {
|
||||||
logger.Info(args...)
|
logger.Load().Info(args...)
|
||||||
addToBuffer("INFO", fmt.Sprint(args...))
|
addToBuffer("INFO", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infof logs a formatted info message and adds it to the log buffer.
|
// Infof logs a formatted info message and adds it to the log buffer.
|
||||||
func Infof(format string, args ...any) {
|
func Infof(format string, args ...any) {
|
||||||
logger.Infof(format, args...)
|
logger.Load().Infof(format, args...)
|
||||||
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notice logs a notice message and adds it to the log buffer.
|
// Notice logs a notice message and adds it to the log buffer.
|
||||||
func Notice(args ...any) {
|
func Notice(args ...any) {
|
||||||
logger.Notice(args...)
|
logger.Load().Notice(args...)
|
||||||
addToBuffer("NOTICE", fmt.Sprint(args...))
|
addToBuffer("NOTICE", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Noticef logs a formatted notice message and adds it to the log buffer.
|
// Noticef logs a formatted notice message and adds it to the log buffer.
|
||||||
func Noticef(format string, args ...any) {
|
func Noticef(format string, args ...any) {
|
||||||
logger.Noticef(format, args...)
|
logger.Load().Noticef(format, args...)
|
||||||
addToBuffer("NOTICE", fmt.Sprintf(format, args...))
|
addToBuffer("NOTICE", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning logs a warning message and adds it to the log buffer.
|
// Warning logs a warning message and adds it to the log buffer.
|
||||||
func Warning(args ...any) {
|
func Warning(args ...any) {
|
||||||
logger.Warning(args...)
|
logger.Load().Warning(args...)
|
||||||
addToBuffer("WARNING", fmt.Sprint(args...))
|
addToBuffer("WARNING", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warningf logs a formatted warning message and adds it to the log buffer.
|
// Warningf logs a formatted warning message and adds it to the log buffer.
|
||||||
func Warningf(format string, args ...any) {
|
func Warningf(format string, args ...any) {
|
||||||
logger.Warningf(format, args...)
|
logger.Load().Warningf(format, args...)
|
||||||
addToBuffer("WARNING", fmt.Sprintf(format, args...))
|
addToBuffer("WARNING", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error logs an error message and adds it to the log buffer.
|
// Error logs an error message and adds it to the log buffer.
|
||||||
func Error(args ...any) {
|
func Error(args ...any) {
|
||||||
logger.Error(args...)
|
logger.Load().Error(args...)
|
||||||
addToBuffer("ERROR", fmt.Sprint(args...))
|
addToBuffer("ERROR", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorf logs a formatted error message and adds it to the log buffer.
|
// Errorf logs a formatted error message and adds it to the log buffer.
|
||||||
func Errorf(format string, args ...any) {
|
func Errorf(format string, args ...any) {
|
||||||
logger.Errorf(format, args...)
|
logger.Load().Errorf(format, args...)
|
||||||
addToBuffer("ERROR", fmt.Sprintf(format, args...))
|
addToBuffer("ERROR", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ package logger
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
golog "github.com/op/go-logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestGetLogs_ReturnsAtMostC guards the documented "up to c entries" contract.
|
// TestGetLogs_ReturnsAtMostC guards the documented "up to c entries" contract.
|
||||||
@@ -28,3 +31,30 @@ func TestGetLogs_ReturnsAtMostC(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitLogger replaces the package logger while other goroutines are already
|
||||||
|
// logging — CI caught that as a data race between InitLogger and Warningf.
|
||||||
|
func TestInitLoggerConcurrentWithLogging(t *testing.T) {
|
||||||
|
t.Setenv("XUI_LOG_FOLDER", t.TempDir())
|
||||||
|
|
||||||
|
stop := make(chan struct{})
|
||||||
|
var logging sync.WaitGroup
|
||||||
|
logging.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer logging.Done()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
Warningf("concurrent %s", "log")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for range 10 {
|
||||||
|
InitLogger(golog.CRITICAL)
|
||||||
|
}
|
||||||
|
close(stop)
|
||||||
|
logging.Wait()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user