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 +}