infoalloggi-monorepo/apps/backend/utils/fails.go

79 lines
2 KiB
Go
Raw Normal View History

2026-01-13 10:53:27 +01:00
package utils
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"time"
)
const (
debugDir = "failed_requests"
maxSaveBody = 5 * 1024 * 1024 // 5 MiB, configurable
filePerm = 0640
dirPerm = 0750
timestampFormat = "20060102T150405Z" // safe for filenames
)
// helper: generate a small random hex id
func randomHex(n int) string {
b := make([]byte, n)
_, _ = rand.Read(b)
return hex.EncodeToString(b)
}
// save a failing request atomically with metadata
func saveFailedRequest(prefix, url string, reqHeaders http.Header, respHeaders http.Header, status int, body []byte, errMsg string) (string, error) {
if err := os.MkdirAll(debugDir, dirPerm); err != nil {
return "", fmt.Errorf("mkdir debug dir: %w", err)
}
ts := time.Now().UTC().Format(timestampFormat)
id := randomHex(6)
base := fmt.Sprintf("%s_%s_%s", prefix, ts, id)
// Truncate body if too large
truncated := false
if len(body) > maxSaveBody {
body = body[:maxSaveBody]
truncated = true
}
meta := map[string]interface{}{
"timestamp_utc": ts,
"url": url,
"status": status,
"error": errMsg,
"truncated": truncated,
"req_headers": reqHeaders,
"resp_headers": respHeaders,
}
metaBytes, _ := json.MarshalIndent(meta, "", " ")
metaTmp := filepath.Join(debugDir, base+".meta.json.tmp")
bodyTmp := filepath.Join(debugDir, base+".body.tmp")
metaPath := filepath.Join(debugDir, base+".meta.json")
bodyPath := filepath.Join(debugDir, base+".body")
// write temp + rename (atomic on POSIX filesystems)
if err := os.WriteFile(metaTmp, metaBytes, filePerm); err != nil {
return "", fmt.Errorf("write meta tmp: %w", err)
}
if err := os.Rename(metaTmp, metaPath); err != nil {
return "", fmt.Errorf("rename meta: %w", err)
}
if err := os.WriteFile(bodyTmp, body, filePerm); err != nil {
return "", fmt.Errorf("write body tmp: %w", err)
}
if err := os.Rename(bodyTmp, bodyPath); err != nil {
return "", fmt.Errorf("rename body: %w", err)
}
return base, nil
}