Browse Source

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.
MHSanaei 10 hours ago
parent
commit
7368359924
1 changed files with 54 additions and 0 deletions
  1. 54 0
      web/service/xray.go

+ 54 - 0
web/service/xray.go

@@ -3,12 +3,15 @@ package service
 import (
 import (
 	"encoding/json"
 	"encoding/json"
 	"errors"
 	"errors"
+	"path/filepath"
 	"runtime"
 	"runtime"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 
 
+	"github.com/mhsanaei/3x-ui/v3/config"
 	"github.com/mhsanaei/3x-ui/v3/database/model"
 	"github.com/mhsanaei/3x-ui/v3/database/model"
 	"github.com/mhsanaei/3x-ui/v3/logger"
 	"github.com/mhsanaei/3x-ui/v3/logger"
+	"github.com/mhsanaei/3x-ui/v3/util/json_util"
 	"github.com/mhsanaei/3x-ui/v3/xray"
 	"github.com/mhsanaei/3x-ui/v3/xray"
 
 
 	"go.uber.org/atomic"
 	"go.uber.org/atomic"
@@ -104,6 +107,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	xrayConfig.LogConfig = resolveXrayLogPaths(xrayConfig.LogConfig)
 
 
 	_, _, _ = s.inboundService.AddTraffic(nil, nil)
 	_, _, _ = s.inboundService.AddTraffic(nil, nil)
 
 
@@ -253,6 +257,56 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
 	return xrayConfig, nil
 	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
 // healShadowsocksClientMethods is the same idea as applyShadowsocksClientMethod
 // (see client.go) but applied at xray-config-build time, to backfill the
 // (see client.go) but applied at xray-config-build time, to backfill the
 // per-client method field for legacy shadowsocks inbounds whose clients were
 // per-client method field for legacy shadowsocks inbounds whose clients were