feat: nlog, config hot-reload, and concurrency fixes

This commit is contained in:
xboard
2026-03-24 23:12:55 +08:00
parent 191e58b9ee
commit 7e3ad7cb23
22 changed files with 572 additions and 281 deletions
+3 -3
View File
@@ -3,11 +3,11 @@ package xray
import (
"encoding/base64"
"fmt"
"log/slog"
"strings"
"github.com/cedar2025/xboard-node/internal/config"
"github.com/cedar2025/xboard-node/internal/kernel"
"github.com/cedar2025/xboard-node/internal/nlog"
"github.com/cedar2025/xboard-node/internal/panel"
)
@@ -70,7 +70,7 @@ func buildConfig(kcfg config.KernelConfig, nc *panel.NodeConfig, users []panel.U
if inbound != nil {
cfg["inbounds"] = []M{inbound}
} else {
slog.Warn("xray: unsupported protocol, no inbound configured — node will not accept connections",
nlog.Core().Warn("xray: unsupported protocol, no inbound configured — node will not accept connections",
"protocol", nc.Protocol,
"supported", "vmess, vless, trojan, shadowsocks, socks, http")
}
@@ -116,7 +116,7 @@ func mergeRouteList(a, b []map[string]any) []map[string]any {
func mergeCustomXray(cfg M, kcfg config.KernelConfig) {
custom, err := kernel.LoadCustomConfig(kcfg.CustomConfig)
if err != nil {
slog.Error("failed to load custom xray config", "error", err)
nlog.Core().Error("failed to load custom xray config", "error", err)
return
}
if custom == nil {
+4 -3
View File
@@ -3,7 +3,6 @@ package xray
import (
"context"
"errors"
"log/slog"
"reflect"
"sort"
"sync"
@@ -18,6 +17,8 @@ import (
"github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/transport"
"golang.org/x/time/rate"
"github.com/cedar2025/xboard-node/internal/nlog"
)
// Access xray's internal config creator registry so we can replace the
@@ -54,7 +55,7 @@ func limitDispatcherFactory(ctx context.Context, config interface{}) (interface{
limitedIPs: make(map[string]map[string]int),
}
globalLimitDispatcher.Store(ld)
slog.Debug("xray: limit dispatcher installed")
nlog.Core().Debug("xray: limit dispatcher installed")
return ld, nil
}
@@ -158,7 +159,7 @@ func (d *LimitDispatcher) identifyAndCheck(ctx context.Context, dest net.Destina
isTCP = dest.Network == net.Network_TCP
if d.checkDeviceLimit(email, sourceIP, isTCP) {
slog.Info("xray: device limit exceeded", "email", email, "ip", sourceIP)
nlog.Core().Info("xray: device limit exceeded", "email", email, "ip", sourceIP)
return "", "", false, errors.New("device limit exceeded for " + email)
}
return email, sourceIP, isTCP, nil
+24 -21
View File
@@ -6,7 +6,6 @@ import (
"crypto/sha256"
"encoding/json"
"fmt"
"log/slog"
"os"
"strings"
"sync"
@@ -32,6 +31,7 @@ import (
"github.com/cedar2025/xboard-node/internal/config"
"github.com/cedar2025/xboard-node/internal/kernel"
"github.com/cedar2025/xboard-node/internal/kernel/geodata"
"github.com/cedar2025/xboard-node/internal/nlog"
"github.com/cedar2025/xboard-node/internal/panel"
)
@@ -153,7 +153,7 @@ func (x *Xray) Start(nodeConfig *panel.NodeConfig, users []panel.User, certFile,
x.updateDispatcherLimits(users)
slog.Info("xray started",
nlog.Core().Info("xray started",
"users", len(users),
"protocol", nodeConfig.Protocol,
)
@@ -174,12 +174,12 @@ func (x *Xray) Reload(nodeConfig *panel.NodeConfig, users []panel.User, certFile
if same {
x.users = users
x.mu.Unlock()
slog.Debug("xray: limits updated, kernel configuration unchanged")
nlog.Core().Debug("xray: limits updated, kernel configuration unchanged")
return nil
}
x.mu.Unlock()
slog.Info("xray: configuration changed, performing full restart")
nlog.Core().Info("xray: configuration changed, performing full restart")
return x.Start(nodeConfig, users, certFile, keyFile)
}
@@ -220,8 +220,10 @@ func (x *Xray) GetUserTraffic(_ context.Context) (traffic map[int][2]int64, aliv
return traffic, aliveIPs, connCount, nil
}
// Fallback: read xray's native stats counters
// Fallback path: aggregateStats needs x.mu.
x.mu.Lock()
traffic, err = x.aggregateStats()
x.mu.Unlock()
return traffic, nil, 0, err
}
@@ -289,7 +291,7 @@ func (x *Xray) AddUsers(users []panel.User) (int, error) {
// Protocol doesn't support UserManager → full restart
nc, cf, kf := x.nodeConfig, x.certFile, x.keyFile
x.mu.Unlock()
slog.Debug("xray: AddUsers fallback to restart", "reason", err)
nlog.Core().Debug("xray: AddUsers fallback to restart", "reason", err)
if err := x.Start(nc, merged, cf, kf); err != nil {
return 0, err
}
@@ -305,11 +307,11 @@ func (x *Xray) AddUsers(users []panel.User) (int, error) {
for _, u := range toAdd {
mu, err := toMemoryUser(proto, nc, u)
if err != nil {
slog.Warn("xray: skip user, cannot build account", "user", u.ID, "error", err)
nlog.Core().Warn("xray: skip user, cannot build account", "user", u.ID, "error", err)
continue
}
if err := um.AddUser(ctx, mu); err != nil {
slog.Warn("xray: AddUser failed", "user", u.ID, "error", err)
nlog.Core().Warn("xray: AddUser failed", "user", u.ID, "error", err)
continue
}
added++
@@ -321,7 +323,7 @@ func (x *Xray) AddUsers(users []panel.User) (int, error) {
x.mu.Unlock()
x.updateDispatcherLimits(merged)
slog.Info("xray: users added via UserManager", "added", added, "total", len(merged))
nlog.Core().Info("xray: users added via UserManager", "added", added, "total", len(merged))
return added, nil
}
@@ -361,7 +363,7 @@ func (x *Xray) RemoveUsers(users []panel.User) (int, error) {
if err != nil {
nc, cf, kf := x.nodeConfig, x.certFile, x.keyFile
x.mu.Unlock()
slog.Debug("xray: RemoveUsers fallback to restart", "reason", err)
nlog.Core().Debug("xray: RemoveUsers fallback to restart", "reason", err)
if err := x.Start(nc, kept, cf, kf); err != nil {
return 0, err
}
@@ -374,7 +376,7 @@ func (x *Xray) RemoveUsers(users []panel.User) (int, error) {
for _, u := range users {
email := userEmail(u.ID)
if err := um.RemoveUser(ctx, email); err != nil {
slog.Debug("xray: RemoveUser skipped", "user", u.ID, "error", err)
nlog.Core().Debug("xray: RemoveUser skipped", "user", u.ID, "error", err)
continue
}
actualRemoved++
@@ -385,7 +387,7 @@ func (x *Xray) RemoveUsers(users []panel.User) (int, error) {
x.mu.Unlock()
x.updateDispatcherLimits(kept)
slog.Info("xray: users removed via UserManager", "removed", actualRemoved, "total", len(kept))
nlog.Core().Info("xray: users removed via UserManager", "removed", actualRemoved, "total", len(kept))
return actualRemoved, nil
}
@@ -406,7 +408,7 @@ func (x *Xray) UpdateUsers(users []panel.User) (added, removed int, err error) {
x.users = users
x.mu.Unlock()
x.updateDispatcherLimits(users)
slog.Info("xray: limits updated, kernel unchanged")
nlog.Core().Info("xray: limits updated, kernel unchanged")
return 0, 0, nil
}
@@ -415,7 +417,7 @@ func (x *Xray) UpdateUsers(users []panel.User) (added, removed int, err error) {
// Protocol doesn't support UserManager → full restart
nc, cf, kf := x.nodeConfig, x.certFile, x.keyFile
x.mu.Unlock()
slog.Debug("xray: UpdateUsers fallback to restart", "reason", umErr)
nlog.Core().Debug("xray: UpdateUsers fallback to restart", "reason", umErr)
if err = x.Start(nc, users, cf, kf); err != nil {
return 0, 0, err
}
@@ -432,17 +434,17 @@ func (x *Xray) UpdateUsers(users []panel.User) (added, removed int, err error) {
for _, u := range toRemove {
email := userEmail(u.ID)
if err := um.RemoveUser(ctx, email); err != nil {
slog.Debug("xray: RemoveUser skipped in UpdateUsers", "user", u.ID, "error", err)
nlog.Core().Debug("xray: RemoveUser skipped in UpdateUsers", "user", u.ID, "error", err)
}
}
for _, u := range toAdd {
mu, err := toMemoryUser(proto, nc, u)
if err != nil {
slog.Warn("xray: skip user in UpdateUsers", "user", u.ID, "error", err)
nlog.Core().Warn("xray: skip user in UpdateUsers", "user", u.ID, "error", err)
continue
}
if err := um.AddUser(ctx, mu); err != nil {
slog.Warn("xray: AddUser failed in UpdateUsers", "user", u.ID, "error", err)
nlog.Core().Warn("xray: AddUser failed in UpdateUsers", "user", u.ID, "error", err)
}
}
@@ -451,7 +453,7 @@ func (x *Xray) UpdateUsers(users []panel.User) (added, removed int, err error) {
x.mu.Unlock()
x.updateDispatcherLimits(users)
slog.Info("xray: users updated via UserManager", "added", added, "removed", removed, "total", len(users))
nlog.Core().Info("xray: users updated via UserManager", "added", added, "removed", removed, "total", len(users))
return
}
@@ -597,7 +599,7 @@ func (x *Xray) ensureGeoData(nc *panel.NodeConfig) {
}
dir := x.cfg.GeoDataDir
if err := geodata.Ensure(dir, needIP, needSite, "xray"); err != nil {
slog.Warn("geo database unavailable", "error", err)
nlog.Core().Warn("geo database unavailable", "error", err)
}
os.Setenv("XRAY_LOCATION_ASSET", dir)
}
@@ -609,7 +611,7 @@ func marshalConfig(cfg config.KernelConfig, nc *panel.NodeConfig, users []panel.
if err != nil {
return nil, fmt.Errorf("marshal config: %w", err)
}
slog.Debug("xray config generated", "len", len(data))
nlog.Core().Debug("xray config generated", "len", len(data))
return data, nil
}
@@ -625,6 +627,7 @@ func startWithTimeout(inst *xrayCore.Instance, timeout time.Duration) error {
}
return nil
case <-time.After(timeout):
go inst.Close()
return fmt.Errorf("start xray: timeout after %v", timeout)
}
}
@@ -648,7 +651,7 @@ func closeOld(inst *xrayCore.Instance, ld *LimitDispatcher) {
if ld != nil {
ld.ResetConns()
}
slog.Debug("xray: old instance recycled")
nlog.Core().Debug("xray: old instance recycled")
}()
}