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.
This commit is contained in:
MHSanaei
2026-05-21 19:15:24 +02:00
parent f2f5d584b3
commit 7368359924

View File

@@ -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