From 3d2f702ae55ba1c15acfe90e4d38f9b6051c8547 Mon Sep 17 00:00:00 2001 From: Galen Kistler <109082771+gtk-grafana@users.noreply.github.com> Date: Fri, 21 Nov 2025 04:40:29 -0600 Subject: [PATCH] Loki: fix status in body not matching http status code (#114201) * fix: status in body not matching http status code --- pkg/tsdb/loki/api.go | 6 ++++- pkg/tsdb/loki/api_test.go | 49 ++++++++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/pkg/tsdb/loki/api.go b/pkg/tsdb/loki/api.go index 464a267391a..eb7f72e6570 100644 --- a/pkg/tsdb/loki/api.go +++ b/pkg/tsdb/loki/api.go @@ -199,6 +199,7 @@ func (api *LokiAPI) DataQuery(ctx context.Context, query lokiQuery, responseOpts res := backend.DataResponse{ Error: err, ErrorSource: backend.ErrorSourceFromHTTPStatus(resp.StatusCode), + Status: backend.Status(resp.StatusCode), } lp = append(lp, "status", "error", "error", err, "statusSource", res.ErrorSource) api.log.Debug("Error received from Loki", lp...) @@ -214,6 +215,7 @@ func (api *LokiAPI) DataQuery(ctx context.Context, query lokiQuery, responseOpts iter := jsoniter.Parse(jsoniter.ConfigDefault, resp.Body, 1024) res := converter.ReadPrometheusStyleResult(iter, converter.Options{}) + res.Status = backend.Status(resp.StatusCode) if res.Error != nil { span.RecordError(res.Error) @@ -305,7 +307,9 @@ func (api *LokiAPI) RawQuery(ctx context.Context, resourcePath string) (RawLokiR } body, err = json.Marshal(lokiResponseErr) if err != nil { - return RawLokiResponse{}, err + return RawLokiResponse{ + Status: resp.StatusCode, + }, err } } diff --git a/pkg/tsdb/loki/api_test.go b/pkg/tsdb/loki/api_test.go index 252608780e6..29b15e7fb18 100644 --- a/pkg/tsdb/loki/api_test.go +++ b/pkg/tsdb/loki/api_test.go @@ -250,6 +250,7 @@ func TestApiReturnValues(t *testing.T) { require.True(t, called) require.Equal(t, "gzip", encodedBytes.Encoding) require.Equal(t, []byte("{\"message\":\"foo\"}"), encodedBytes.Body) + require.Equal(t, 400, encodedBytes.Status) }) t.Run("Loki should return the error as is", func(t *testing.T) { @@ -280,6 +281,18 @@ func TestApiReturnValues(t *testing.T) { require.Error(t, err) require.ErrorContains(t, err, "foo") }) + + t.Run("should set status for successful requests", func(t *testing.T) { + called := false + api := makeMockedAPI(200, "application/json", []byte("{\"message\":\"foo\"}"), func(req *http.Request) { + called = true + }) + + res, err := api.DataQuery(context.Background(), lokiQuery{Expr: "", SupportingQueryType: SupportingQueryLogsVolume, QueryType: QueryTypeRange}, ResponseOpts{}) + require.NoError(t, err) + require.True(t, called) + require.Equal(t, backend.Status(http.StatusOK), res.Status) + }) } func TestErrorSources(t *testing.T) { @@ -287,7 +300,7 @@ func TestErrorSources(t *testing.T) { t.Run("should set correct error source for downstream errors", func(t *testing.T) { called := false - api := makeMockedAPI(400, "application/json", errorResponse, func(req *http.Request) { + api := makeMockedAPI(http.StatusBadRequest, "application/json", errorResponse, func(req *http.Request) { called = true }) @@ -296,11 +309,12 @@ func TestErrorSources(t *testing.T) { require.True(t, called) require.NotNil(t, res.Error) require.Equal(t, backend.ErrorSourceDownstream, res.ErrorSource) + require.Equal(t, backend.Status(http.StatusBadRequest), res.Status) }) t.Run("should set correct error source for plugin errors", func(t *testing.T) { called := false - api := makeMockedAPI(406, "application/json", errorResponse, func(req *http.Request) { + api := makeMockedAPI(http.StatusNotAcceptable, "application/json", errorResponse, func(req *http.Request) { called = true }) @@ -309,11 +323,12 @@ func TestErrorSources(t *testing.T) { require.True(t, called) require.NotNil(t, res.Error) require.Equal(t, backend.ErrorSourcePlugin, res.ErrorSource) + require.Equal(t, backend.Status(http.StatusNotAcceptable), res.Status) }) t.Run("should set correct error source for server errors", func(t *testing.T) { called := false - api := makeMockedAPI(500, "application/json", errorResponse, func(req *http.Request) { + api := makeMockedAPI(http.StatusInternalServerError, "application/json", errorResponse, func(req *http.Request) { called = true }) @@ -322,11 +337,26 @@ func TestErrorSources(t *testing.T) { require.True(t, called) require.NotNil(t, res.Error) require.Equal(t, backend.ErrorSourceDownstream, res.ErrorSource) + require.Equal(t, backend.Status(http.StatusInternalServerError), res.Status) + }) + + t.Run("should set correct error source for server timeout error", func(t *testing.T) { + called := false + api := makeMockedAPI(http.StatusGatewayTimeout, "application/json", errorResponse, func(req *http.Request) { + called = true + }) + + res, err := api.DataQuery(context.Background(), lokiQuery{QueryType: QueryTypeRange}, ResponseOpts{}) + require.NoError(t, err) + require.True(t, called) + require.NotNil(t, res.Error) + require.Equal(t, backend.ErrorSourceDownstream, res.ErrorSource) + require.Equal(t, backend.Status(http.StatusGatewayTimeout), res.Status) }) t.Run("should handle downstream HTTP errors", func(t *testing.T) { called := false - api := makeMockedAPI(400, "application/json", errorResponse, func(req *http.Request) { + api := makeMockedAPI(http.StatusBadRequest, "application/json", errorResponse, func(req *http.Request) { called = true }) @@ -336,30 +366,33 @@ func TestErrorSources(t *testing.T) { require.NotNil(t, res.Error) require.Equal(t, backend.ErrorSourceDownstream, res.ErrorSource) require.Contains(t, res.Error.Error(), "test error") + require.Equal(t, backend.Status(http.StatusBadRequest), res.Status) }) t.Run("should handle client errors in RawQuery", func(t *testing.T) { called := false - api := makeMockedAPI(400, "application/json", errorResponse, func(req *http.Request) { + api := makeMockedAPI(http.StatusBadRequest, "application/json", errorResponse, func(req *http.Request) { called = true }) res, err := api.RawQuery(context.Background(), "/loki/api/v1/labels") require.NoError(t, err) require.True(t, called) - require.Equal(t, 400, res.Status) + require.Equal(t, http.StatusBadRequest, res.Status) require.Contains(t, string(res.Body), "test error") }) t.Run("should handle server errors in RawQuery", func(t *testing.T) { called := false - api := makeMockedAPI(500, "application/json", errorResponse, func(req *http.Request) { + api := makeMockedAPI(http.StatusInternalServerError, "application/json", errorResponse, func(req *http.Request) { called = true }) - _, err := api.RawQuery(context.Background(), "/loki/api/v1/labels") + res, err := api.RawQuery(context.Background(), "/loki/api/v1/labels") require.Error(t, err) require.True(t, called) require.Contains(t, err.Error(), "test error") + // Status code of 0 gets mapped to InternalServerError (500) + require.Equal(t, 0, res.Status) }) }