logger.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. // Package logger provides logging functionality for the 3x-ui panel with
  2. // dual-backend logging (console/syslog and file) and buffered log storage for web UI.
  3. package logger
  4. import (
  5. "fmt"
  6. "os"
  7. "path/filepath"
  8. "runtime"
  9. "time"
  10. "github.com/mhsanaei/3x-ui/v2/config"
  11. "github.com/op/go-logging"
  12. )
  13. const (
  14. maxLogBufferSize = 10240 // Maximum log entries kept in memory
  15. logFileName = "3xui.log" // Log file name
  16. timeFormat = "2006/01/02 15:04:05" // Log timestamp format
  17. )
  18. var (
  19. logger *logging.Logger
  20. logFile *os.File
  21. // logBuffer maintains recent log entries in memory for web UI retrieval
  22. logBuffer []struct {
  23. time string
  24. level logging.Level
  25. log string
  26. }
  27. )
  28. // InitLogger initializes dual logging backends: console/syslog and file.
  29. // Console logging uses the specified level, file logging always uses DEBUG level.
  30. func InitLogger(level logging.Level) {
  31. newLogger := logging.MustGetLogger("x-ui")
  32. backends := make([]logging.Backend, 0, 2)
  33. // Console/syslog backend with configurable level
  34. if consoleBackend := initDefaultBackend(); consoleBackend != nil {
  35. leveledBackend := logging.AddModuleLevel(consoleBackend)
  36. leveledBackend.SetLevel(level, "x-ui")
  37. backends = append(backends, leveledBackend)
  38. }
  39. // File backend with DEBUG level for comprehensive logging
  40. if fileBackend := initFileBackend(); fileBackend != nil {
  41. leveledBackend := logging.AddModuleLevel(fileBackend)
  42. leveledBackend.SetLevel(logging.DEBUG, "x-ui")
  43. backends = append(backends, leveledBackend)
  44. }
  45. multiBackend := logging.MultiLogger(backends...)
  46. newLogger.SetBackend(multiBackend)
  47. logger = newLogger
  48. }
  49. // initDefaultBackend creates the console/syslog logging backend.
  50. // Windows: Uses stderr directly (no syslog support)
  51. // Unix-like: Attempts syslog, falls back to stderr
  52. func initDefaultBackend() logging.Backend {
  53. var backend logging.Backend
  54. includeTime := false
  55. if runtime.GOOS == "windows" {
  56. // Windows: Use stderr directly (no syslog support)
  57. backend = logging.NewLogBackend(os.Stderr, "", 0)
  58. includeTime = true
  59. } else {
  60. // Unix-like: Try syslog, fallback to stderr
  61. if syslogBackend, err := logging.NewSyslogBackend(""); err != nil {
  62. fmt.Fprintf(os.Stderr, "syslog backend disabled: %v\n", err)
  63. backend = logging.NewLogBackend(os.Stderr, "", 0)
  64. includeTime = os.Getppid() > 0
  65. } else {
  66. backend = syslogBackend
  67. }
  68. }
  69. return logging.NewBackendFormatter(backend, newFormatter(includeTime))
  70. }
  71. // initFileBackend creates the file logging backend.
  72. // Creates log directory and truncates log file on startup for fresh logs.
  73. func initFileBackend() logging.Backend {
  74. logDir := config.GetLogFolder()
  75. if err := os.MkdirAll(logDir, 0o750); err != nil {
  76. fmt.Fprintf(os.Stderr, "failed to create log folder %s: %v\n", logDir, err)
  77. return nil
  78. }
  79. logPath := filepath.Join(logDir, logFileName)
  80. file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o660)
  81. if err != nil {
  82. fmt.Fprintf(os.Stderr, "failed to open log file %s: %v\n", logPath, err)
  83. return nil
  84. }
  85. // Close previous log file if exists
  86. if logFile != nil {
  87. _ = logFile.Close()
  88. }
  89. logFile = file
  90. backend := logging.NewLogBackend(file, "", 0)
  91. return logging.NewBackendFormatter(backend, newFormatter(true))
  92. }
  93. // newFormatter creates a log formatter with optional timestamp.
  94. func newFormatter(withTime bool) logging.Formatter {
  95. format := `%{level} - %{message}`
  96. if withTime {
  97. format = `%{time:` + timeFormat + `} %{level} - %{message}`
  98. }
  99. return logging.MustStringFormatter(format)
  100. }
  101. // CloseLogger closes the log file and cleans up resources.
  102. // Should be called during application shutdown.
  103. func CloseLogger() {
  104. if logFile != nil {
  105. _ = logFile.Close()
  106. logFile = nil
  107. }
  108. }
  109. // Debug logs a debug message and adds it to the log buffer.
  110. func Debug(args ...any) {
  111. logger.Debug(args...)
  112. addToBuffer("DEBUG", fmt.Sprint(args...))
  113. }
  114. // Debugf logs a formatted debug message and adds it to the log buffer.
  115. func Debugf(format string, args ...any) {
  116. logger.Debugf(format, args...)
  117. addToBuffer("DEBUG", fmt.Sprintf(format, args...))
  118. }
  119. // Info logs an info message and adds it to the log buffer.
  120. func Info(args ...any) {
  121. logger.Info(args...)
  122. addToBuffer("INFO", fmt.Sprint(args...))
  123. }
  124. // Infof logs a formatted info message and adds it to the log buffer.
  125. func Infof(format string, args ...any) {
  126. logger.Infof(format, args...)
  127. addToBuffer("INFO", fmt.Sprintf(format, args...))
  128. }
  129. // Notice logs a notice message and adds it to the log buffer.
  130. func Notice(args ...any) {
  131. logger.Notice(args...)
  132. addToBuffer("NOTICE", fmt.Sprint(args...))
  133. }
  134. // Noticef logs a formatted notice message and adds it to the log buffer.
  135. func Noticef(format string, args ...any) {
  136. logger.Noticef(format, args...)
  137. addToBuffer("NOTICE", fmt.Sprintf(format, args...))
  138. }
  139. // Warning logs a warning message and adds it to the log buffer.
  140. func Warning(args ...any) {
  141. logger.Warning(args...)
  142. addToBuffer("WARNING", fmt.Sprint(args...))
  143. }
  144. // Warningf logs a formatted warning message and adds it to the log buffer.
  145. func Warningf(format string, args ...any) {
  146. logger.Warningf(format, args...)
  147. addToBuffer("WARNING", fmt.Sprintf(format, args...))
  148. }
  149. // Error logs an error message and adds it to the log buffer.
  150. func Error(args ...any) {
  151. logger.Error(args...)
  152. addToBuffer("ERROR", fmt.Sprint(args...))
  153. }
  154. // Errorf logs a formatted error message and adds it to the log buffer.
  155. func Errorf(format string, args ...any) {
  156. logger.Errorf(format, args...)
  157. addToBuffer("ERROR", fmt.Sprintf(format, args...))
  158. }
  159. // addToBuffer adds a log entry to the in-memory ring buffer for web UI retrieval.
  160. func addToBuffer(level string, newLog string) {
  161. t := time.Now()
  162. if len(logBuffer) >= maxLogBufferSize {
  163. logBuffer = logBuffer[1:]
  164. }
  165. logLevel, _ := logging.LogLevel(level)
  166. logBuffer = append(logBuffer, struct {
  167. time string
  168. level logging.Level
  169. log string
  170. }{
  171. time: t.Format(timeFormat),
  172. level: logLevel,
  173. log: newLog,
  174. })
  175. }
  176. // GetLogs retrieves up to c log entries from the buffer that are at or below the specified level.
  177. func GetLogs(c int, level string) []string {
  178. var output []string
  179. logLevel, _ := logging.LogLevel(level)
  180. for i := len(logBuffer) - 1; i >= 0 && len(output) <= c; i-- {
  181. if logBuffer[i].level <= logLevel {
  182. output = append(output, fmt.Sprintf("%s %s - %s", logBuffer[i].time, logBuffer[i].level, logBuffer[i].log))
  183. }
  184. }
  185. return output
  186. }