diff --git a/server/cmd/main.go b/server/cmd/main.go
index e6fd5b457..6380ed6ff 100644
--- a/server/cmd/main.go
+++ b/server/cmd/main.go
@@ -77,8 +77,8 @@ func main() {
 	settings.HttpAuth = params.HttpAuth
 	log.Init(params.LogPath, params.WebLogPath)
 
-	fmt.Println("=========== START ===========")
-	fmt.Println("TorrServer", version.Version+",", runtime.Version()+",", "CPU Num:", runtime.NumCPU())
+	log.TLogln("=========== START ===========")
+	log.TLogln("TorrServer", version.Version+",", runtime.Version()+",", "CPU Num:", runtime.NumCPU())
 	if params.HttpAuth {
 		log.TLogln("Use HTTP Auth file", settings.Path+"/accs.db")
 	}
diff --git a/server/log/log.go b/server/log/log.go
index 64d796e40..f294cf9fb 100644
--- a/server/log/log.go
+++ b/server/log/log.go
@@ -6,6 +6,7 @@ import (
 	"io"
 	"log"
 	"os"
+	"path/filepath"
 	"strings"
 
 	"github.com/gin-gonic/gin"
@@ -24,13 +25,34 @@ var (
 )
 
 func Init(path, webpath string) {
-	webLogPath = webpath
 	logPath = path
+	webLogPath = webpath
+
+	shared := path != "" && webpath != "" && filepath.Clean(path) == filepath.Clean(webpath)
+	if path != "" {
+		path = filepath.Clean(path)
+	}
+	if webpath != "" {
+		webpath = filepath.Clean(webpath)
+	}
+
+	if shared {
+		ff, err := openLogFile(path)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "Error create log file: %v\n", err)
+			return
+		}
+		logFile = ff
+		webLogFile = ff
+		webLog = log.New(ff, " ", log.LstdFlags)
+		applyServerLog(ff)
+		return
+	}
 
 	if webpath != "" {
-		ff, err := os.OpenFile(webLogPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666)
+		ff, err := os.OpenFile(webpath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666)
 		if err != nil {
-			TLogln("Error create web log file:", err)
+			fmt.Fprintf(os.Stderr, "Error create web log file: %v\n", err)
 		} else {
 			webLogFile = ff
 			webLog = log.New(ff, " ", log.LstdFlags)
@@ -38,39 +60,47 @@ func Init(path, webpath string) {
 	}
 
 	if path != "" {
-		if fi, err := os.Lstat(path); err == nil {
-			if fi.Size() >= 100*1024*1024 { // 100MB
-				os.Remove(path)
-			}
-		}
-		ff, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666)
+		ff, err := openLogFile(path)
 		if err != nil {
-			TLogln("Error create log file:", err)
+			fmt.Fprintf(os.Stderr, "Error create log file: %v\n", err)
 			return
 		}
 		logFile = ff
-		os.Stdout = ff
-		os.Stderr = ff
-		// var timeFmt string
-		// var ok bool
-		// timeFmt, ok = os.LookupEnv("GO_LOG_TIME_FMT")
-		// if !ok {
-		// 	timeFmt = "2006-01-02T15:04:05-0700"
-		// }
-		// log.SetFlags(log.Lmsgprefix)
-		// log.SetPrefix(time.Now().Format(timeFmt) + " TSM ")
-		log.SetFlags(log.LstdFlags | log.LUTC | log.Lmsgprefix)
-		log.SetPrefix("UTC0 ")
-		log.SetOutput(ff)
+		applyServerLog(ff)
 	}
 }
 
+func openLogFile(path string) (*os.File, error) {
+	if fi, err := os.Lstat(path); err == nil {
+		if fi.Size() >= 100*1024*1024 { // 100MB
+			os.Remove(path)
+		}
+	}
+	return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666)
+}
+
+func applyServerLog(ff *os.File) {
+	if err := redirectStdFDs(ff); err != nil {
+		fmt.Fprintf(os.Stderr, "Error redirect stdout/stderr: %v\n", err)
+	}
+	log.SetFlags(log.LstdFlags | log.LUTC | log.Lmsgprefix)
+	log.SetPrefix("UTC0 ")
+	log.SetOutput(ff)
+}
+
 func Close() {
 	if logFile != nil {
 		logFile.Close()
+		if webLogFile == logFile {
+			webLogFile = nil
+			webLog = nil
+		}
+		logFile = nil
 	}
 	if webLogFile != nil {
 		webLogFile.Close()
+		webLogFile = nil
+		webLog = nil
 	}
 }
 
@@ -93,8 +123,9 @@ func WebLogger() gin.HandlerFunc {
 		body := ""
 		// save body if not form or file
 		if !strings.HasPrefix(c.Request.Header.Get("Content-Type"), "multipart/form-data") {
-			body, _ := io.ReadAll(c.Request.Body)
-			c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
+			bodyBytes, _ := io.ReadAll(c.Request.Body)
+			c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
+			body = string(bodyBytes)
 		} else {
 			body = "body hidden, too large"
 		}
@@ -114,7 +145,7 @@ func WebLogger() gin.HandlerFunc {
 			clientIP,
 			method,
 			path,
-			string(body),
+			body,
 		)
 		WebLogln(logStr)
 	}
diff --git a/server/log/log_test.go b/server/log/log_test.go
new file mode 100644
index 000000000..9184ddc3d
--- /dev/null
+++ b/server/log/log_test.go
@@ -0,0 +1,52 @@
+package log
+
+import (
+	"path/filepath"
+	"testing"
+)
+
+func TestInitSharedLogPath(t *testing.T) {
+	dir := t.TempDir()
+	path := filepath.Join(dir, "combined.log")
+
+	Init(path, path)
+	defer Close()
+
+	if logFile == nil {
+		t.Fatal("expected logFile to be set")
+	}
+	if webLogFile != logFile {
+		t.Fatal("expected webLogFile to share logFile when paths are equal")
+	}
+	if webLog == nil {
+		t.Fatal("expected webLog to be set")
+	}
+}
+
+func TestInitSeparateLogPaths(t *testing.T) {
+	dir := t.TempDir()
+	serverPath := filepath.Join(dir, "server.log")
+	webPath := filepath.Join(dir, "web.log")
+
+	Init(serverPath, webPath)
+	defer Close()
+
+	if logFile == nil || webLogFile == nil {
+		t.Fatal("expected both log files to be set")
+	}
+	if logFile == webLogFile {
+		t.Fatal("expected separate file handles for different paths")
+	}
+}
+
+func TestCloseSharedLogPathOnce(t *testing.T) {
+	dir := t.TempDir()
+	path := filepath.Join(dir, "combined.log")
+
+	Init(path, path)
+	Close()
+
+	if logFile != nil || webLogFile != nil || webLog != nil {
+		t.Fatal("expected log handles to be cleared after Close")
+	}
+}
diff --git a/server/log/redirect_unix.go b/server/log/redirect_unix.go
new file mode 100644
index 000000000..c82a8691a
--- /dev/null
+++ b/server/log/redirect_unix.go
@@ -0,0 +1,22 @@
+//go:build !windows
+
+package log
+
+import (
+	"os"
+
+	"golang.org/x/sys/unix"
+)
+
+func redirectStdFDs(f *os.File) error {
+	fd := int(f.Fd())
+	if err := unix.Dup2(fd, 1); err != nil {
+		return err
+	}
+	if err := unix.Dup2(fd, 2); err != nil {
+		return err
+	}
+	os.Stdout = f
+	os.Stderr = f
+	return nil
+}
diff --git a/server/log/redirect_windows.go b/server/log/redirect_windows.go
new file mode 100644
index 000000000..4ff8e1803
--- /dev/null
+++ b/server/log/redirect_windows.go
@@ -0,0 +1,11 @@
+//go:build windows
+
+package log
+
+import "os"
+
+func redirectStdFDs(f *os.File) error {
+	os.Stdout = f
+	os.Stderr = f
+	return nil
+}
