diff --git a/go.mod b/go.mod index afa2004c..b3c988d0 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( golang.org/x/sys v0.44.0 golang.org/x/text v0.37.0 google.golang.org/grpc v1.81.0 + gopkg.in/natefinch/lumberjack.v2 v2.2.1 gorm.io/driver/sqlite v1.6.0 gorm.io/gorm v1.31.1 ) diff --git a/go.sum b/go.sum index ca60ec1e..f4dfd091 100644 --- a/go.sum +++ b/go.sum @@ -265,6 +265,8 @@ google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/logger/logger.go b/logger/logger.go index d665396a..8cc96fc6 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -11,17 +11,25 @@ import ( "github.com/mhsanaei/3x-ui/v3/config" "github.com/op/go-logging" + + "gopkg.in/natefinch/lumberjack.v2" ) const ( maxLogBufferSize = 10240 // Maximum log entries kept in memory logFileName = "3xui.log" // Log file name timeFormat = "2006/01/02 15:04:05" // Log timestamp format + + // On-disk rotation limits — single file capped, old segments pruned automatically. + maxLogFileMB = 10 // rotate active log when larger than this + maxLogBackups = 5 // rotated files retained (beyond current segment) + maxLogAgeDays = 7 // remove rotated backups older than this (0 disables time-based pruning) + compressRotated = true ) var ( - logger *logging.Logger - logFile *os.File + logger *logging.Logger + fileRotate *lumberjack.Logger // nil when file backend disabled // logBuffer maintains recent log entries in memory for web UI retrieval logBuffer []struct { @@ -81,8 +89,8 @@ func initDefaultBackend() logging.Backend { return logging.NewBackendFormatter(backend, newFormatter(includeTime)) } -// initFileBackend creates the file logging backend. -// Creates log directory and truncates log file on startup for fresh logs. +// initFileBackend creates the file logging backend with size/age‑bounded rotation +// so log volume cannot grow without limit on disk. func initFileBackend() logging.Backend { logDir := config.GetLogFolder() if err := os.MkdirAll(logDir, 0o750); err != nil { @@ -91,19 +99,16 @@ func initFileBackend() logging.Backend { } logPath := filepath.Join(logDir, logFileName) - file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o660) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to open log file %s: %v\n", logPath, err) - return nil + fileRotate = &lumberjack.Logger{ + Filename: logPath, + MaxSize: maxLogFileMB, + MaxBackups: maxLogBackups, + MaxAge: maxLogAgeDays, + LocalTime: true, + Compress: compressRotated, } - // Close previous log file if exists - if logFile != nil { - _ = logFile.Close() - } - logFile = file - - backend := logging.NewLogBackend(file, "", 0) + backend := logging.NewLogBackend(fileRotate, "", 0) return logging.NewBackendFormatter(backend, newFormatter(true)) } @@ -116,12 +121,12 @@ func newFormatter(withTime bool) logging.Formatter { return logging.MustStringFormatter(format) } -// CloseLogger closes the log file and cleans up resources. +// CloseLogger closes the rotating log writer and cleans up resources. // Should be called during application shutdown. func CloseLogger() { - if logFile != nil { - _ = logFile.Close() - logFile = nil + if fileRotate != nil { + _ = fileRotate.Close() + fileRotate = nil } }