// backend\log.go package main import ( "fmt" "io" "net/http" "os" "path/filepath" "strings" "sync" "time" ) var ( appLogMu sync.Mutex appLogFile *os.File ) func appLogPath() string { exePath, _ := os.Executable() exeDir := "" if exePath != "" { exeDir = filepath.Dir(exePath) } if exeDir == "" { exeDir, _ = os.Getwd() } return filepath.Join(exeDir, "recorder.log") } func openAppLogFile() (*os.File, error) { path := appLogPath() if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { return nil, err } return os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) } func initAppLog() { appLogMu.Lock() defer appLogMu.Unlock() if appLogFile != nil { return } f, err := openAppLogFile() if err != nil { // Wichtig: hier NICHT appLogln verwenden, weil appLogMu schon gelockt ist. fmt.Println("⚠️ recorder log konnte nicht geöffnet werden:", err) return } appLogFile = f _, _ = fmt.Fprintf( appLogFile, "\n\n--- Recorder Start %s ---\nlog=%s\n", time.Now().Format(time.RFC3339), appLogPath(), ) _ = appLogFile.Sync() } func closeAppLog() { appLogMu.Lock() defer appLogMu.Unlock() if appLogFile == nil { return } _, _ = fmt.Fprintf( appLogFile, "--- Recorder Stop %s ---\n", time.Now().Format(time.RFC3339), ) _ = appLogFile.Sync() _ = appLogFile.Close() appLogFile = nil } func appLogWriter() io.Writer { initAppLog() appLogMu.Lock() defer appLogMu.Unlock() if appLogFile != nil { return appLogFile } return os.Stderr } func appLogWriteLine(line string) { line = strings.TrimRight(line, "\r\n") ts := time.Now().Format("2006-01-02 15:04:05") out := fmt.Sprintf("[%s] %s\n", ts, line) appLogMu.Lock() defer appLogMu.Unlock() fmt.Print(out) if appLogFile != nil { _, _ = appLogFile.WriteString(out) _ = appLogFile.Sync() } } func appLogln(args ...any) { appLogWriteLine(fmt.Sprintln(args...)) } func appLogf(format string, args ...any) { appLogWriteLine(fmt.Sprintf(format, args...)) } func appErrorf(format string, args ...any) error { err := fmt.Errorf(format, args...) appLogln("❌", err) return err } // Nur zentraler Ersatz für fmt.Sprintf. // Loggt absichtlich NICHT, weil Sprintf oft nur UI-/Command-Strings baut. func appSprintf(format string, args ...any) string { return fmt.Sprintf(format, args...) } // Zentraler Ersatz für fmt.Sprint. func appSprint(args ...any) string { return fmt.Sprint(args...) } func appLogHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Nur GET erlaubt", http.StatusMethodNotAllowed) return } path := appLogPath() maxBytes := int64(64 * 1024) switch strings.TrimSpace(r.URL.Query().Get("maxBytes")) { case "16384": maxBytes = 16 * 1024 case "32768": maxBytes = 32 * 1024 case "131072": maxBytes = 128 * 1024 case "262144": maxBytes = 256 * 1024 } info, err := os.Stat(path) if err != nil { if os.IsNotExist(err) { writeJSON(w, http.StatusOK, map[string]any{ "ok": false, "path": path, "exists": false, "log": "", "error": "Logdatei existiert noch nicht.", }) return } writeJSON(w, http.StatusOK, map[string]any{ "ok": false, "path": path, "exists": false, "log": "", "error": err.Error(), }) return } if info.IsDir() { writeJSON(w, http.StatusOK, map[string]any{ "ok": false, "path": path, "exists": true, "log": "", "error": "Logpfad ist ein Ordner.", }) return } f, err := os.Open(path) if err != nil { writeJSON(w, http.StatusOK, map[string]any{ "ok": false, "path": path, "exists": true, "log": "", "error": err.Error(), }) return } defer f.Close() size := info.Size() offset := int64(0) if size > maxBytes { offset = size - maxBytes } buf := make([]byte, size-offset) if len(buf) > 0 { if _, err := f.ReadAt(buf, offset); err != nil && err != io.EOF { writeJSON(w, http.StatusOK, map[string]any{ "ok": false, "path": path, "exists": true, "log": "", "error": err.Error(), }) return } } logText := string(buf) if offset > 0 { logText = "… Log gekürzt, zeige die letzten " + fmt.Sprint(maxBytes/1024) + " KB …\n\n" + logText } writeJSON(w, http.StatusOK, map[string]any{ "ok": true, "path": path, "exists": true, "sizeBytes": size, "log": logText, }) } func clearAppLog() { appLogMu.Lock() defer appLogMu.Unlock() if appLogFile != nil { _ = appLogFile.Close() appLogFile = nil } path := appLogPath() if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { fmt.Println("⚠️ recorder log dir konnte nicht erstellt werden:", err) return } if err := os.WriteFile(path, []byte{}, 0644); err != nil { fmt.Println("⚠️ recorder log konnte nicht geleert werden:", err) } }