diff --git a/api.go b/api.go index 8b3fc77..e3e86a7 100644 --- a/api.go +++ b/api.go @@ -75,7 +75,7 @@ func (a *API) logs(w http.ResponseWriter, r *http.Request) { start := time.Now() rows, err := a.store.Query(r.Context(), q) if err != nil { - writeErr(w, http.StatusBadGateway, err) + writeErr(w, queryStatus(err), err) return } a.annotateHosts(rows) @@ -105,7 +105,7 @@ func (a *API) histogram(w http.ResponseWriter, r *http.Request) { q := filter + " | stats by (_time:" + step.expr + ") count() hits | sort by (_time)" rows, err := a.store.Query(r.Context(), q) if err != nil { - writeErr(w, http.StatusBadGateway, err) + writeErr(w, queryStatus(err), err) return } type bucket struct { @@ -168,7 +168,7 @@ func (a *API) facets(w http.ResponseWriter, r *http.Request) { q := "_time:7d | stats by (" + field + ") count() hits | sort by (hits desc) | limit 300" rows, err := a.store.Query(r.Context(), q) if err != nil { - writeErr(w, http.StatusBadGateway, err) + writeErr(w, queryStatus(err), err) return } vals := make([]string, 0, len(rows)) diff --git a/export.go b/export.go index 0132e68..2d6b1a9 100644 --- a/export.go +++ b/export.go @@ -114,7 +114,7 @@ func (a *API) exportCSV(w http.ResponseWriter, r *http.Request) { }) if err != nil { if cw == nil { - writeErr(w, http.StatusBadGateway, err) + writeErr(w, queryStatus(err), err) return } // Headers are already sent: the file is truncated, log why. diff --git a/store.go b/store.go index ca37758..b7eafdd 100644 --- a/store.go +++ b/store.go @@ -5,6 +5,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "log" @@ -170,7 +171,7 @@ func (s *Store) QueryStream(ctx context.Context, query string, fn func(row map[s defer resp.Body.Close() if resp.StatusCode != http.StatusOK { msg, _ := io.ReadAll(io.LimitReader(resp.Body, 2048)) - return fmt.Errorf("%s", strings.TrimSpace(string(msg))) + return &queryError{status: resp.StatusCode, msg: strings.TrimSpace(string(msg))} } sc := bufio.NewScanner(resp.Body) @@ -245,6 +246,24 @@ func (s *Store) deleteCall(ctx context.Context, path string) ([]byte, error) { return body, nil } +// queryError is an error answer of VictoriaLogs to a query. +type queryError struct { + status int + msg string +} + +func (e *queryError) Error() string { return e.msg } + +// queryStatus is the HTTP status to return for a failed query: 400 when +// VictoriaLogs rejected the query itself (bad LogsQL), 502 otherwise. +func queryStatus(err error) int { + var qe *queryError + if errors.As(err, &qe) && qe.status >= 400 && qe.status < 500 { + return http.StatusBadRequest + } + return http.StatusBadGateway +} + func (s *Store) Stats() map[string]any { return map[string]any{ "received": s.received.Load(),