|
@@ -1,21 +1,29 @@
|
|
|
// Package logger provides logging functionality for the 3x-ui panel with
|
|
|
-// buffered log storage and multiple log levels.
|
|
|
+// dual-backend logging (console/syslog and file) and buffered log storage for web UI.
|
|
|
package logger
|
|
|
|
|
|
import (
|
|
|
"fmt"
|
|
|
"os"
|
|
|
+ "path/filepath"
|
|
|
+ "runtime"
|
|
|
"time"
|
|
|
|
|
|
+ "github.com/mhsanaei/3x-ui/v2/config"
|
|
|
"github.com/op/go-logging"
|
|
|
)
|
|
|
|
|
|
+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
|
|
|
+)
|
|
|
+
|
|
|
var (
|
|
|
- logger *logging.Logger
|
|
|
+ logger *logging.Logger
|
|
|
+ logFile *os.File
|
|
|
|
|
|
- // addToBuffer appends a log entry into the in-memory ring buffer used for
|
|
|
- // retrieving recent logs via the web UI. It keeps the buffer bounded to avoid
|
|
|
- // uncontrolled growth.
|
|
|
+ // logBuffer maintains recent log entries in memory for web UI retrieval
|
|
|
logBuffer []struct {
|
|
|
time string
|
|
|
level logging.Level
|
|
@@ -23,35 +31,98 @@ var (
|
|
|
}
|
|
|
)
|
|
|
|
|
|
-func init() {
|
|
|
- InitLogger(logging.INFO)
|
|
|
-}
|
|
|
-
|
|
|
-// InitLogger initializes the logger with the specified logging level.
|
|
|
+// InitLogger initializes dual logging backends: console/syslog and file.
|
|
|
+// Console logging uses the specified level, file logging always uses DEBUG level.
|
|
|
func InitLogger(level logging.Level) {
|
|
|
newLogger := logging.MustGetLogger("x-ui")
|
|
|
- var err error
|
|
|
+ backends := make([]logging.Backend, 0, 2)
|
|
|
+
|
|
|
+ // Console/syslog backend with configurable level
|
|
|
+ if consoleBackend := initDefaultBackend(); consoleBackend != nil {
|
|
|
+ leveledBackend := logging.AddModuleLevel(consoleBackend)
|
|
|
+ leveledBackend.SetLevel(level, "x-ui")
|
|
|
+ backends = append(backends, leveledBackend)
|
|
|
+ }
|
|
|
+
|
|
|
+ // File backend with DEBUG level for comprehensive logging
|
|
|
+ if fileBackend := initFileBackend(); fileBackend != nil {
|
|
|
+ leveledBackend := logging.AddModuleLevel(fileBackend)
|
|
|
+ leveledBackend.SetLevel(logging.DEBUG, "x-ui")
|
|
|
+ backends = append(backends, leveledBackend)
|
|
|
+ }
|
|
|
+
|
|
|
+ multiBackend := logging.MultiLogger(backends...)
|
|
|
+ newLogger.SetBackend(multiBackend)
|
|
|
+ logger = newLogger
|
|
|
+}
|
|
|
+
|
|
|
+// initDefaultBackend creates the console/syslog logging backend.
|
|
|
+// Windows: Uses stderr directly (no syslog support)
|
|
|
+// Unix-like: Attempts syslog, falls back to stderr
|
|
|
+func initDefaultBackend() logging.Backend {
|
|
|
var backend logging.Backend
|
|
|
- var format logging.Formatter
|
|
|
- ppid := os.Getppid()
|
|
|
+ includeTime := false
|
|
|
|
|
|
- backend, err = logging.NewSyslogBackend("")
|
|
|
- if err != nil {
|
|
|
- println(err)
|
|
|
+ if runtime.GOOS == "windows" {
|
|
|
+ // Windows: Use stderr directly (no syslog support)
|
|
|
backend = logging.NewLogBackend(os.Stderr, "", 0)
|
|
|
- }
|
|
|
- if ppid > 0 && err != nil {
|
|
|
- format = logging.MustStringFormatter(`%{time:2006/01/02 15:04:05} %{level} - %{message}`)
|
|
|
+ includeTime = true
|
|
|
} else {
|
|
|
- format = logging.MustStringFormatter(`%{level} - %{message}`)
|
|
|
+ // Unix-like: Try syslog, fallback to stderr
|
|
|
+ if syslogBackend, err := logging.NewSyslogBackend(""); err != nil {
|
|
|
+ fmt.Fprintf(os.Stderr, "syslog backend disabled: %v\n", err)
|
|
|
+ backend = logging.NewLogBackend(os.Stderr, "", 0)
|
|
|
+ includeTime = os.Getppid() > 0
|
|
|
+ } else {
|
|
|
+ backend = syslogBackend
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- backendFormatter := logging.NewBackendFormatter(backend, format)
|
|
|
- backendLeveled := logging.AddModuleLevel(backendFormatter)
|
|
|
- backendLeveled.SetLevel(level, "x-ui")
|
|
|
- newLogger.SetBackend(backendLeveled)
|
|
|
+ return logging.NewBackendFormatter(backend, newFormatter(includeTime))
|
|
|
+}
|
|
|
|
|
|
- logger = newLogger
|
|
|
+// initFileBackend creates the file logging backend.
|
|
|
+// Creates log directory and truncates log file on startup for fresh logs.
|
|
|
+func initFileBackend() logging.Backend {
|
|
|
+ logDir := config.GetLogFolder()
|
|
|
+ if err := os.MkdirAll(logDir, 0o750); err != nil {
|
|
|
+ fmt.Fprintf(os.Stderr, "failed to create log folder %s: %v\n", logDir, err)
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ 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
|
|
|
+ }
|
|
|
+
|
|
|
+ // Close previous log file if exists
|
|
|
+ if logFile != nil {
|
|
|
+ _ = logFile.Close()
|
|
|
+ }
|
|
|
+ logFile = file
|
|
|
+
|
|
|
+ backend := logging.NewLogBackend(file, "", 0)
|
|
|
+ return logging.NewBackendFormatter(backend, newFormatter(true))
|
|
|
+}
|
|
|
+
|
|
|
+// newFormatter creates a log formatter with optional timestamp.
|
|
|
+func newFormatter(withTime bool) logging.Formatter {
|
|
|
+ format := `%{level} - %{message}`
|
|
|
+ if withTime {
|
|
|
+ format = `%{time:` + timeFormat + `} %{level} - %{message}`
|
|
|
+ }
|
|
|
+ return logging.MustStringFormatter(format)
|
|
|
+}
|
|
|
+
|
|
|
+// CloseLogger closes the log file and cleans up resources.
|
|
|
+// Should be called during application shutdown.
|
|
|
+func CloseLogger() {
|
|
|
+ if logFile != nil {
|
|
|
+ _ = logFile.Close()
|
|
|
+ logFile = nil
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// Debug logs a debug message and adds it to the log buffer.
|
|
@@ -114,9 +185,10 @@ func Errorf(format string, args ...any) {
|
|
|
addToBuffer("ERROR", fmt.Sprintf(format, args...))
|
|
|
}
|
|
|
|
|
|
+// addToBuffer adds a log entry to the in-memory ring buffer for web UI retrieval.
|
|
|
func addToBuffer(level string, newLog string) {
|
|
|
t := time.Now()
|
|
|
- if len(logBuffer) >= 10240 {
|
|
|
+ if len(logBuffer) >= maxLogBufferSize {
|
|
|
logBuffer = logBuffer[1:]
|
|
|
}
|
|
|
|
|
@@ -126,7 +198,7 @@ func addToBuffer(level string, newLog string) {
|
|
|
level logging.Level
|
|
|
log string
|
|
|
}{
|
|
|
- time: t.Format("2006/01/02 15:04:05"),
|
|
|
+ time: t.Format(timeFormat),
|
|
|
level: logLevel,
|
|
|
log: newLog,
|
|
|
})
|