From 0f8645aed45dfa03c07ff3e73ea2da9296347d41 Mon Sep 17 00:00:00 2001 From: Igor Mino Date: Sun, 14 Jun 2026 15:51:44 +0200 Subject: [PATCH] pridane logovanie promptov --- main.go | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 185 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index e45ade9..f472f5f 100644 --- a/main.go +++ b/main.go @@ -4,13 +4,19 @@ import ( "bufio" "bytes" "context" + "crypto/sha256" + "encoding/hex" "encoding/json" "errors" + "fmt" + "io" "log" "net/http" "os" "os/exec" + "path/filepath" "strings" + "sync/atomic" "time" ) @@ -51,6 +57,11 @@ type commandResult struct { const geminiTimeout = 5 * time.Minute +var ( + requestLogRoot = "/var/log/daiapi" + requestLogCounter uint64 +) + func main() { mux := http.NewServeMux() mux.HandleFunc("/run", runCodexHandler) @@ -63,11 +74,184 @@ func main() { } log.Printf("listening on %s", addr) - if err := http.ListenAndServe(addr, mux); err != nil { + if err := http.ListenAndServe(addr, requestLoggingMiddleware(mux)); err != nil { log.Fatal(err) } } +func requestLoggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + receivedAt := time.Now() + body, readErr := io.ReadAll(r.Body) + if closeErr := r.Body.Close(); closeErr != nil { + log.Printf("failed to close request body: %v", closeErr) + } + if readErr != nil { + log.Printf("failed to read request body for logging: %v", readErr) + } + r.Body = io.NopCloser(bytes.NewReader(body)) + + logFile, logPath, logErr := createRequestLogFile(receivedAt, r.URL.Path, body) + if logErr != nil { + log.Printf("failed to create request log file: %v", logErr) + } else { + defer func() { + if err := logFile.Close(); err != nil { + log.Printf("failed to close request log file %s: %v", logPath, err) + } + }() + writeRequestLogStart(logFile, receivedAt, r, body, readErr) + } + + lrw := &loggingResponseWriter{ + ResponseWriter: w, + statusCode: http.StatusOK, + } + + next.ServeHTTP(lrw, r) + + if logFile != nil { + writeRequestLogEnd(logFile, time.Now(), lrw.statusCode, lrw.body.Bytes(), nil) + } + }) +} + +func createRequestLogFile(receivedAt time.Time, requestPath string, body []byte) (*os.File, string, error) { + dir := filepath.Join( + requestLogRoot, + receivedAt.Format("2006"), + receivedAt.Format("01"), + receivedAt.Format("02"), + ) + if err := os.MkdirAll(dir, 0750); err != nil { + return nil, "", err + } + + for attempts := 0; attempts < 5; attempts++ { + hash := requestLogHash(receivedAt, requestPath, body) + name := fmt.Sprintf("%s.%s.log", receivedAt.Format("15-04-05"), hash) + path := filepath.Join(dir, name) + file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0640) + if err == nil { + return file, path, nil + } + if !errors.Is(err, os.ErrExist) { + return nil, "", err + } + } + + return nil, "", fmt.Errorf("failed to create unique request log file after retries") +} + +func requestLogHash(receivedAt time.Time, requestPath string, body []byte) string { + counter := atomic.AddUint64(&requestLogCounter, 1) + hash := sha256.New() + fmt.Fprintf(hash, "%s\n%s\n%d\n", receivedAt.Format(time.RFC3339Nano), requestPath, counter) + hash.Write(body) + return hex.EncodeToString(hash.Sum(nil))[:16] +} + +func writeRequestLogStart(file *os.File, receivedAt time.Time, r *http.Request, body []byte, readErr error) { + entry := map[string]interface{}{ + "timestamp": receivedAt.Format(time.RFC3339Nano), + "method": r.Method, + "path": r.URL.Path, + "query": r.URL.Query(), + "headers": redactedHeaders(r.Header), + "body": string(body), + } + if readErr != nil { + entry["bodyReadError"] = readErr.Error() + } + + writeLogSection(file, "request", entry) +} + +func writeRequestLogEnd(file *os.File, finishedAt time.Time, statusCode int, body []byte, processingErr error) { + entry := map[string]interface{}{ + "timestamp": finishedAt.Format(time.RFC3339Nano), + "statusCode": statusCode, + "responseBody": string(body), + } + if processingErr != nil { + entry["error"] = processingErr.Error() + } else if statusCode >= http.StatusBadRequest { + entry["error"] = http.StatusText(statusCode) + } + + writeLogSection(file, "response", entry) +} + +func writeLogSection(file *os.File, section string, entry map[string]interface{}) { + payload, err := json.MarshalIndent(entry, "", " ") + if err != nil { + log.Printf("failed to marshal %s log section: %v", section, err) + return + } + + if _, err := fmt.Fprintf(file, "=== %s ===\n%s\n\n", section, payload); err != nil { + log.Printf("failed to write %s log section: %v", section, err) + } +} + +func redactedHeaders(headers http.Header) http.Header { + redacted := make(http.Header, len(headers)) + for key, values := range headers { + if isSensitiveHeader(key) { + redacted[key] = []string{"[redacted]"} + continue + } + redacted[key] = append([]string(nil), values...) + } + return redacted +} + +func isSensitiveHeader(key string) bool { + switch strings.ToLower(key) { + case "authorization", "cookie", "set-cookie", "x-api-key", "x-auth-token": + return true + default: + return false + } +} + +type loggingResponseWriter struct { + http.ResponseWriter + statusCode int + wroteHeader bool + body bytes.Buffer +} + +func (w *loggingResponseWriter) WriteHeader(statusCode int) { + if w.wroteHeader { + return + } + w.statusCode = statusCode + w.wroteHeader = true + w.ResponseWriter.WriteHeader(statusCode) +} + +func (w *loggingResponseWriter) Write(data []byte) (int, error) { + if !w.wroteHeader { + w.WriteHeader(http.StatusOK) + } + + n, err := w.ResponseWriter.Write(data) + if n > 0 { + w.body.Write(data[:n]) + } + return n, err +} + +func (w *loggingResponseWriter) Flush() { + if flusher, ok := w.ResponseWriter.(http.Flusher); ok { + if !w.wroteHeader { + w.WriteHeader(http.StatusOK) + } + flusher.Flush() + } +} + func runCodexHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed)