From 600698e12a957309cb015dc2f57da3b586242fef Mon Sep 17 00:00:00 2001 From: mkelvers Date: Sat, 11 Apr 2026 22:20:06 +0200 Subject: [PATCH] fix: add detailed watch-order logs --- internal/jikan/relations.go | 13 +++++++++ internal/watchorder/watch_order.go | 35 ++++++++++++++++++++++++- internal/watchorder/watch_order_test.go | 33 +++++++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/internal/jikan/relations.go b/internal/jikan/relations.go index 61fded9..308772f 100644 --- a/internal/jikan/relations.go +++ b/internal/jikan/relations.go @@ -50,8 +50,21 @@ func (c *Client) getWatchOrder(ctx context.Context, id int) (watchorder.WatchOrd result, err := watchorder.FetchWatchOrder(requestCtx, c.httpClient, watchOrderURL) if err != nil { + var statusError *watchorder.HTTPStatusError if errors.Is(err, watchorder.ErrWatchOrderMarkupNotFound) { log.Printf("relations: watch-order markup missing for %d (%s): %v", id, watchOrderURL, err) + } else if errors.As(err, &statusError) { + log.Printf( + "relations: watch-order http error for %d (%s): status=%d server=%q cf_ray=%q location=%q content_type=%q body=%q", + id, + watchOrderURL, + statusError.StatusCode, + statusError.Server, + statusError.CFRay, + statusError.Location, + statusError.ContentType, + statusError.BodyPreview, + ) } else { log.Printf("relations: watch-order fetch failed for %d (%s): %v", id, watchOrderURL, err) } diff --git a/internal/watchorder/watch_order.go b/internal/watchorder/watch_order.go index 731a759..23ff4f6 100644 --- a/internal/watchorder/watch_order.go +++ b/internal/watchorder/watch_order.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io" "net/http" "regexp" "strconv" @@ -19,6 +20,29 @@ var idPattern = regexp.MustCompile(`/id/(\d+)`) var ErrInvalidWatchOrderURL = errors.New("invalid watch order url") var ErrWatchOrderMarkupNotFound = errors.New("watch order markup not found") +type HTTPStatusError struct { + StatusCode int + URL string + Server string + CFRay string + Location string + ContentType string + BodyPreview string +} + +func (e *HTTPStatusError) Error() string { + return fmt.Sprintf( + "unexpected status code: %d (url=%s server=%s cf_ray=%s location=%s content_type=%s body=%q)", + e.StatusCode, + e.URL, + e.Server, + e.CFRay, + e.Location, + e.ContentType, + e.BodyPreview, + ) +} + type WatchOrderEntry struct { ID int `json:"id"` Type string `json:"type"` @@ -76,7 +100,16 @@ func fetchDocument(ctx context.Context, httpClient *http.Client, url string) (*g defer response.Body.Close() if response.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected status code: %d", response.StatusCode) + body, _ := io.ReadAll(io.LimitReader(response.Body, 512)) + return nil, &HTTPStatusError{ + StatusCode: response.StatusCode, + URL: url, + Server: strings.TrimSpace(response.Header.Get("Server")), + CFRay: strings.TrimSpace(response.Header.Get("CF-Ray")), + Location: strings.TrimSpace(response.Header.Get("Location")), + ContentType: strings.TrimSpace(response.Header.Get("Content-Type")), + BodyPreview: strings.Join(strings.Fields(strings.TrimSpace(string(body))), " "), + } } document, err := goquery.NewDocumentFromReader(response.Body) diff --git a/internal/watchorder/watch_order_test.go b/internal/watchorder/watch_order_test.go index 77139ab..077e8fa 100644 --- a/internal/watchorder/watch_order_test.go +++ b/internal/watchorder/watch_order_test.go @@ -5,6 +5,7 @@ import ( "errors" "net/http" "net/http/httptest" + "strings" "testing" "time" ) @@ -125,3 +126,35 @@ func TestFetchWatchOrder_MissingMarkupReturnsError(t *testing.T) { t.Fatalf("expected ErrWatchOrderMarkupNotFound, got %v", err) } } + +func TestFetchWatchOrder_HTTPStatusErrorIncludesContext(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Server", "cloudflare") + w.Header().Set("CF-Ray", "abc123") + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusForbidden) + _, _ = w.Write([]byte("access denied")) + })) + defer server.Close() + + url := server.URL + "/?/tools/watch_order/id/1" + _, err := FetchWatchOrder(context.Background(), &http.Client{Timeout: time.Second}, url) + if err == nil { + t.Fatalf("expected error, got nil") + } + + var statusError *HTTPStatusError + if !errors.As(err, &statusError) { + t.Fatalf("expected HTTPStatusError, got %T", err) + } + + if statusError.StatusCode != http.StatusForbidden { + t.Fatalf("expected 403, got %d", statusError.StatusCode) + } + if statusError.CFRay != "abc123" { + t.Fatalf("expected cf-ray abc123, got %q", statusError.CFRay) + } + if !strings.Contains(statusError.BodyPreview, "access denied") { + t.Fatalf("expected body preview to include access denied, got %q", statusError.BodyPreview) + } +}