log_writer.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. package xray
  2. import (
  3. "regexp"
  4. "runtime"
  5. "strings"
  6. "sync"
  7. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  8. )
  9. // Compiled once at package load: Write runs on every line Xray emits, so
  10. // recompiling these per write is wasted work.
  11. var (
  12. crashRegex = regexp.MustCompile(`(?i)(panic|exception|stack trace|fatal error)`)
  13. logLineRegex = regexp.MustCompile(`^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}\.\d{6}) \[([^\]]+)\] (.+)$`)
  14. )
  15. // NewLogWriter returns a new LogWriter for processing Xray log output.
  16. func NewLogWriter() *LogWriter {
  17. return &LogWriter{}
  18. }
  19. // LogWriter processes and filters log output from the Xray process, handling crash detection and message filtering.
  20. type LogWriter struct {
  21. mu sync.RWMutex
  22. lastLine string
  23. }
  24. // LastLine returns the most recently processed Xray log line. It is safe for
  25. // concurrent use: Process.GetResult reads it from a different goroutine than the
  26. // one Xray drives Write from.
  27. func (lw *LogWriter) LastLine() string {
  28. lw.mu.RLock()
  29. defer lw.mu.RUnlock()
  30. return lw.lastLine
  31. }
  32. func (lw *LogWriter) setLastLine(line string) {
  33. lw.mu.Lock()
  34. lw.lastLine = line
  35. lw.mu.Unlock()
  36. }
  37. // Write processes and filters log output from the Xray process, handling crash detection and message filtering.
  38. func (lw *LogWriter) Write(m []byte) (n int, err error) {
  39. // Convert the data to a string
  40. message := strings.TrimSpace(string(m))
  41. msgLowerAll := strings.ToLower(message)
  42. // Suppress noisy Windows process-kill signal that surfaces as exit status 1
  43. if runtime.GOOS == "windows" && strings.Contains(msgLowerAll, "exit status 1") {
  44. return len(m), nil
  45. }
  46. // Check if the message contains a crash
  47. if crashRegex.MatchString(message) {
  48. logger.Debug("Core crash detected:\n", message)
  49. lw.setLastLine(message)
  50. err1 := writeCrashReport(m)
  51. if err1 != nil {
  52. logger.Error("Unable to write crash report:", err1)
  53. }
  54. return len(m), nil
  55. }
  56. messages := strings.SplitSeq(message, "\n")
  57. for msg := range messages {
  58. matches := logLineRegex.FindStringSubmatch(msg)
  59. if len(matches) > 3 {
  60. level := matches[2]
  61. msgBody := matches[3]
  62. msgBodyLower := strings.ToLower(msgBody)
  63. if strings.Contains(msgBodyLower, "tls handshake error") ||
  64. strings.Contains(msgBodyLower, "connection ends") {
  65. logger.Debug("XRAY: " + msgBody)
  66. lw.setLastLine("")
  67. continue
  68. }
  69. if strings.Contains(msgBodyLower, "failed") {
  70. logger.Error("XRAY: " + msgBody)
  71. } else {
  72. switch level {
  73. case "Debug":
  74. logger.Debug("XRAY: " + msgBody)
  75. case "Info":
  76. logger.Info("XRAY: " + msgBody)
  77. case "Warning":
  78. logger.Warning("XRAY: " + msgBody)
  79. case "Error":
  80. logger.Error("XRAY: " + msgBody)
  81. default:
  82. logger.Debug("XRAY: " + msg)
  83. }
  84. }
  85. lw.setLastLine("")
  86. } else if msg != "" {
  87. msgLower := strings.ToLower(msg)
  88. if strings.Contains(msgLower, "tls handshake error") ||
  89. strings.Contains(msgLower, "connection ends") {
  90. logger.Debug("XRAY: " + msg)
  91. lw.setLastLine(msg)
  92. continue
  93. }
  94. if strings.Contains(msgLower, "failed") {
  95. logger.Error("XRAY: " + msg)
  96. } else {
  97. logger.Debug("XRAY: " + msg)
  98. }
  99. lw.setLastLine(msg)
  100. }
  101. }
  102. return len(m), nil
  103. }