From 7368359924a010d89e54e44e9b482115131237d6 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Thu, 21 May 2026 19:15:24 +0200 Subject: [PATCH] fix(xray): resolve relative log paths under panel log folder Rewrite relative `log.access`/`log.error` values in the Xray config to absolute paths under config.GetLogFolder() so Xray writes log files alongside the panel's logs regardless of the panel's working directory. Absolute paths, empty/"none" values, and nested relative paths are left untouched. --- web/service/xray.go | 54 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/web/service/xray.go b/web/service/xray.go index d86da4c0..00b17c55 100644 --- a/web/service/xray.go +++ b/web/service/xray.go @@ -3,12 +3,15 @@ package service import ( "encoding/json" "errors" + "path/filepath" "runtime" "strings" "sync" + "github.com/mhsanaei/3x-ui/v3/config" "github.com/mhsanaei/3x-ui/v3/database/model" "github.com/mhsanaei/3x-ui/v3/logger" + "github.com/mhsanaei/3x-ui/v3/util/json_util" "github.com/mhsanaei/3x-ui/v3/xray" "go.uber.org/atomic" @@ -104,6 +107,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) { if err != nil { return nil, err } + xrayConfig.LogConfig = resolveXrayLogPaths(xrayConfig.LogConfig) _, _, _ = s.inboundService.AddTraffic(nil, nil) @@ -253,6 +257,56 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) { return xrayConfig, nil } +// resolveXrayLogPaths rewrites relative `log.access` / `log.error` values to +// absolute paths under config.GetLogFolder(), so Xray writes those files +// alongside the panel's other logs regardless of the working directory the +// panel was launched from. Values that are empty, "none", or already absolute +// are left untouched, as are unparseable log blocks. +func resolveXrayLogPaths(logCfg json_util.RawMessage) json_util.RawMessage { + if len(logCfg) == 0 { + return logCfg + } + var parsed map[string]any + if err := json.Unmarshal(logCfg, &parsed); err != nil { + return logCfg + } + changed := false + for _, key := range []string{"access", "error"} { + v, ok := parsed[key].(string) + if !ok { + continue + } + trimmed := strings.TrimSpace(v) + if trimmed == "" || strings.EqualFold(trimmed, "none") { + continue + } + if filepath.IsAbs(trimmed) { + continue + } + cleaned := filepath.ToSlash(filepath.Clean(trimmed)) + base := filepath.Base(cleaned) + if base == "" || base == "." || base == string(filepath.Separator) { + continue + } + // Only rewrite bare names ("./access.log", "access.log"). + // A nested relative path like "./logs/foo.log" is treated as + // a deliberate user choice and left alone. + if cleaned != base { + continue + } + parsed[key] = filepath.Join(config.GetLogFolder(), base) + changed = true + } + if !changed { + return logCfg + } + out, err := json.Marshal(parsed) + if err != nil { + return logCfg + } + return out +} + // healShadowsocksClientMethods is the same idea as applyShadowsocksClientMethod // (see client.go) but applied at xray-config-build time, to backfill the // per-client method field for legacy shadowsocks inbounds whose clients were