fix(nodes): log the inbound the node snapshot removes centrally (#6219)

The orphan sweep deletes a central inbound and the traffic history of every
client on it, but wrote nothing. An inbound that vanishes minutes after being
created is then indistinguishable from one that never arrived, and the only
way to tell them apart is reading the source.

Name the node, tag, id and port so the removal is visible in the panel log.

Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
This commit is contained in:
n0ctal
2026-08-16 17:02:50 +05:00
committed by GitHub
parent 5d6d98d1f9
commit 4b0e9f9b60
2 changed files with 53 additions and 0 deletions
+3
View File
@@ -729,6 +729,9 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
if unmanagedTag(c.Tag) { if unmanagedTag(c.Tag) {
continue continue
} }
// This drops the central inbound and its clients' traffic history, so say
// so: silent removal is indistinguishable from an inbound never arriving.
logger.Warningf("setRemoteTraffic: node %d no longer reports inbound %q (id %d, port %d) — removing it centrally", nodeID, c.Tag, c.Id, c.Port)
var goneEmails []string var goneEmails []string
if err := tx.Model(xray.ClientTraffic{}). if err := tx.Model(xray.ClientTraffic{}).
Where("inbound_id = ?", c.Id). Where("inbound_id = ?", c.Id).
@@ -0,0 +1,50 @@
package service
import (
"strconv"
"strings"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/logger"
"github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
"github.com/op/go-logging"
)
// Removing a central inbound also drops its clients' traffic history, so the
// sweep must name what it removed — a silent drop is undiagnosable in the field.
func TestNodeSnapshotSweepLogsRemovedInbound(t *testing.T) {
logger.InitLogger(logging.WARNING)
setupBulkDB(t)
nodeID, _ := setupNodeRuntime(t)
gone := nodeInbound(t, nodeID, 31777, nil)
survivor := nodeInbound(t, nodeID, 31778, nil)
// The snapshot reports only the survivor, so the other row is swept.
snap := &runtime.TrafficSnapshot{Inbounds: []*model.Inbound{{
Tag: survivor.Tag, Port: survivor.Port, Protocol: model.VLESS, Enable: true,
Settings: survivor.Settings,
}}}
if _, err := (&InboundService{}).setRemoteTrafficLocked(nodeID, snap, false); err != nil {
t.Fatalf("setRemoteTrafficLocked: %v", err)
}
var remaining int64
if err := database.GetDB().Model(model.Inbound{}).Where("id = ?", gone.Id).Count(&remaining).Error; err != nil {
t.Fatalf("count swept inbound: %v", err)
}
if remaining != 0 {
t.Fatalf("fixture did not sweep inbound %d; the log assertion below would be meaningless", gone.Id)
}
want := strconv.Itoa(gone.Id)
for _, line := range logger.GetLogs(200, "warning") {
if strings.Contains(line, gone.Tag) && strings.Contains(line, want) {
return
}
}
t.Fatalf("sweep removed inbound %q (id %d) without naming it in the log", gone.Tag, gone.Id)
}