package app import ( "fmt" "io" "log" "os" "path/filepath" "sync" "time" "vrc_osc_go/internal/common" ) type runtimeLogWriter struct { mu sync.Mutex file *os.File } func (w *runtimeLogWriter) Write(p []byte) (int, error) { w.mu.Lock() defer w.mu.Unlock() if w.file == nil { return len(p), nil } return w.file.Write(p) } func setupRuntimeLogger() error { dir := filepath.Join(common.RootDir(), "runtime") if err := os.MkdirAll(dir, 0o755); err != nil { return err } path := filepath.Join(dir, "runtime.log") f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) if err != nil { return err } logWriter := &runtimeLogWriter{file: f} log.SetOutput(io.MultiWriter(os.Stderr, logWriter)) return nil } func appendRuntimeLog(title, text string) error { dir := filepath.Join(common.RootDir(), "runtime") if err := os.MkdirAll(dir, 0o755); err != nil { return err } path := filepath.Join(dir, "runtime.log") body := text if body == "" { body = "(empty)" } f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) if err != nil { return err } defer f.Close() _, err = fmt.Fprintf(f, "\n[%s] %s\n%s\n", time.Now().Format("2006-01-02 15:04:05"), title, body) return err } func AppendRuntimeLog(title, text string) error { return appendRuntimeLog(title, text) } func appendJoinLeaveLog(text string) error { dir := filepath.Join(common.RootDir(), "runtime") if err := os.MkdirAll(dir, 0o755); err != nil { return err } path := filepath.Join(dir, "join_leave.log") body := text if body == "" { body = "(empty)" } f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) if err != nil { return err } defer f.Close() _, err = fmt.Fprintf(f, "\n[%s] VRC JOIN/LEAVE\n%s\n", time.Now().Format("2006-01-02 15:04:05"), body) return err } func appendDesktopJoinLog(title, worldLabel, text string) error { dir := filepath.Join(os.Getenv("USERPROFILE"), "Desktop") if _, err := os.Stat(dir); err != nil { if home, herr := os.UserHomeDir(); herr == nil { dir = filepath.Join(home, "Desktop") } } if err := os.MkdirAll(dir, 0o755); err != nil { dir = filepath.Join(common.RootDir(), "runtime") if mkErr := os.MkdirAll(dir, 0o755); mkErr != nil { return err } } path := filepath.Join(dir, "join.txt") body := text if body == "" { body = "(empty)" } f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644) if err != nil { fallbackDir := filepath.Join(common.RootDir(), "runtime") if mkErr := os.MkdirAll(fallbackDir, 0o755); mkErr != nil { return err } fallbackPath := filepath.Join(fallbackDir, "join.txt") f, err = os.OpenFile(fallbackPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644) if err != nil { return err } path = fallbackPath } defer f.Close() _, err = fmt.Fprintf(f, "%s\nWorld: %s\n\n%s\n", title, worldLabel, body) return err } func AppendDesktopJoinLog(title, worldLabel, text string) error { return appendDesktopJoinLog(title, worldLabel, text) }