123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- // Package logger provides logging functionality for the 3x-ui panel with
- // 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
- logFile *os.File
- // logBuffer maintains recent log entries in memory for web UI retrieval
- logBuffer []struct {
- time string
- level logging.Level
- log string
- }
- )
- // 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")
- 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
- includeTime := false
- if runtime.GOOS == "windows" {
- // Windows: Use stderr directly (no syslog support)
- backend = logging.NewLogBackend(os.Stderr, "", 0)
- includeTime = true
- } else {
- // 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
- }
- }
- return logging.NewBackendFormatter(backend, newFormatter(includeTime))
- }
- // 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.
- func Debug(args ...any) {
- logger.Debug(args...)
- addToBuffer("DEBUG", fmt.Sprint(args...))
- }
- // Debugf logs a formatted debug message and adds it to the log buffer.
- func Debugf(format string, args ...any) {
- logger.Debugf(format, args...)
- addToBuffer("DEBUG", fmt.Sprintf(format, args...))
- }
- // Info logs an info message and adds it to the log buffer.
- func Info(args ...any) {
- logger.Info(args...)
- addToBuffer("INFO", fmt.Sprint(args...))
- }
- // Infof logs a formatted info message and adds it to the log buffer.
- func Infof(format string, args ...any) {
- logger.Infof(format, args...)
- addToBuffer("INFO", fmt.Sprintf(format, args...))
- }
- // Notice logs a notice message and adds it to the log buffer.
- func Notice(args ...any) {
- logger.Notice(args...)
- addToBuffer("NOTICE", fmt.Sprint(args...))
- }
- // Noticef logs a formatted notice message and adds it to the log buffer.
- func Noticef(format string, args ...any) {
- logger.Noticef(format, args...)
- addToBuffer("NOTICE", fmt.Sprintf(format, args...))
- }
- // Warning logs a warning message and adds it to the log buffer.
- func Warning(args ...any) {
- logger.Warning(args...)
- addToBuffer("WARNING", fmt.Sprint(args...))
- }
- // Warningf logs a formatted warning message and adds it to the log buffer.
- func Warningf(format string, args ...any) {
- logger.Warningf(format, args...)
- addToBuffer("WARNING", fmt.Sprintf(format, args...))
- }
- // Error logs an error message and adds it to the log buffer.
- func Error(args ...any) {
- logger.Error(args...)
- addToBuffer("ERROR", fmt.Sprint(args...))
- }
- // Errorf logs a formatted error message and adds it to the log buffer.
- func Errorf(format string, args ...any) {
- logger.Errorf(format, args...)
- 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) >= maxLogBufferSize {
- logBuffer = logBuffer[1:]
- }
- logLevel, _ := logging.LogLevel(level)
- logBuffer = append(logBuffer, struct {
- time string
- level logging.Level
- log string
- }{
- time: t.Format(timeFormat),
- level: logLevel,
- log: newLog,
- })
- }
- // GetLogs retrieves up to c log entries from the buffer that are at or below the specified level.
- func GetLogs(c int, level string) []string {
- var output []string
- logLevel, _ := logging.LogLevel(level)
- for i := len(logBuffer) - 1; i >= 0 && len(output) <= c; i-- {
- if logBuffer[i].level <= logLevel {
- output = append(output, fmt.Sprintf("%s %s - %s", logBuffer[i].time, logBuffer[i].level, logBuffer[i].log))
- }
- }
- return output
- }
|