mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-05 04:14:21 +00:00
2bb29468d8
* perf(xray): compile log/traffic regexps once at package scope GetTraffic recompiled two stats regexps on every traffic tick, and LogWriter.Write recompiled two more on every log line. Hoist all four to package-level vars so they compile once at load instead of per call on hot paths. * fix(xray): guard LogWriter.lastLine against the GetResult reader race Write is driven by the Xray process goroutine while Process.GetResult reads lastLine from the caller's goroutine, so the unsynchronized field is a data race under `go test -race`. Add an RWMutex and route every write through setLastLine; GetResult reads via LastLine(). * fix(xray): bound handler gRPC calls with a deadline AddInbound, DelInbound and the AddUser AlterInbound call used context.Background(), so a hung core connection could block the caller indefinitely (for example while the process restart lock is held). Give them a 10s deadline (handlerRPCTimeout) and a nil-client guard, matching the other handler operations.
122 lines
3.1 KiB
Go
122 lines
3.1 KiB
Go
package xray
|
|
|
|
import (
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
|
)
|
|
|
|
// Compiled once at package load: Write runs on every line Xray emits, so
|
|
// recompiling these per write is wasted work.
|
|
var (
|
|
crashRegex = regexp.MustCompile(`(?i)(panic|exception|stack trace|fatal error)`)
|
|
logLineRegex = regexp.MustCompile(`^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}\.\d{6}) \[([^\]]+)\] (.+)$`)
|
|
)
|
|
|
|
// NewLogWriter returns a new LogWriter for processing Xray log output.
|
|
func NewLogWriter() *LogWriter {
|
|
return &LogWriter{}
|
|
}
|
|
|
|
// LogWriter processes and filters log output from the Xray process, handling crash detection and message filtering.
|
|
type LogWriter struct {
|
|
mu sync.RWMutex
|
|
lastLine string
|
|
}
|
|
|
|
// LastLine returns the most recently processed Xray log line. It is safe for
|
|
// concurrent use: Process.GetResult reads it from a different goroutine than the
|
|
// one Xray drives Write from.
|
|
func (lw *LogWriter) LastLine() string {
|
|
lw.mu.RLock()
|
|
defer lw.mu.RUnlock()
|
|
return lw.lastLine
|
|
}
|
|
|
|
func (lw *LogWriter) setLastLine(line string) {
|
|
lw.mu.Lock()
|
|
lw.lastLine = line
|
|
lw.mu.Unlock()
|
|
}
|
|
|
|
// Write processes and filters log output from the Xray process, handling crash detection and message filtering.
|
|
func (lw *LogWriter) Write(m []byte) (n int, err error) {
|
|
// Convert the data to a string
|
|
message := strings.TrimSpace(string(m))
|
|
msgLowerAll := strings.ToLower(message)
|
|
|
|
// Suppress noisy Windows process-kill signal that surfaces as exit status 1
|
|
if runtime.GOOS == "windows" && strings.Contains(msgLowerAll, "exit status 1") {
|
|
return len(m), nil
|
|
}
|
|
|
|
// Check if the message contains a crash
|
|
if crashRegex.MatchString(message) {
|
|
logger.Debug("Core crash detected:\n", message)
|
|
lw.setLastLine(message)
|
|
err1 := writeCrashReport(m)
|
|
if err1 != nil {
|
|
logger.Error("Unable to write crash report:", err1)
|
|
}
|
|
return len(m), nil
|
|
}
|
|
|
|
messages := strings.SplitSeq(message, "\n")
|
|
|
|
for msg := range messages {
|
|
matches := logLineRegex.FindStringSubmatch(msg)
|
|
|
|
if len(matches) > 3 {
|
|
level := matches[2]
|
|
msgBody := matches[3]
|
|
msgBodyLower := strings.ToLower(msgBody)
|
|
|
|
if strings.Contains(msgBodyLower, "tls handshake error") ||
|
|
strings.Contains(msgBodyLower, "connection ends") {
|
|
logger.Debug("XRAY: " + msgBody)
|
|
lw.setLastLine("")
|
|
continue
|
|
}
|
|
|
|
if strings.Contains(msgBodyLower, "failed") {
|
|
logger.Error("XRAY: " + msgBody)
|
|
} else {
|
|
switch level {
|
|
case "Debug":
|
|
logger.Debug("XRAY: " + msgBody)
|
|
case "Info":
|
|
logger.Info("XRAY: " + msgBody)
|
|
case "Warning":
|
|
logger.Warning("XRAY: " + msgBody)
|
|
case "Error":
|
|
logger.Error("XRAY: " + msgBody)
|
|
default:
|
|
logger.Debug("XRAY: " + msg)
|
|
}
|
|
}
|
|
lw.setLastLine("")
|
|
} else if msg != "" {
|
|
msgLower := strings.ToLower(msg)
|
|
|
|
if strings.Contains(msgLower, "tls handshake error") ||
|
|
strings.Contains(msgLower, "connection ends") {
|
|
logger.Debug("XRAY: " + msg)
|
|
lw.setLastLine(msg)
|
|
continue
|
|
}
|
|
|
|
if strings.Contains(msgLower, "failed") {
|
|
logger.Error("XRAY: " + msg)
|
|
} else {
|
|
logger.Debug("XRAY: " + msg)
|
|
}
|
|
lw.setLastLine(msg)
|
|
}
|
|
}
|
|
|
|
return len(m), nil
|
|
}
|