diff --git a/docs/sources/reference/loki-http-api.md b/docs/sources/reference/loki-http-api.md index 53dba2a6ab1b..6acb11c0e529 100644 --- a/docs/sources/reference/loki-http-api.md +++ b/docs/sources/reference/loki-http-api.md @@ -284,8 +284,9 @@ GET /loki/api/v1/query ``` `/loki/api/v1/query` allows for doing queries against a single point in time. -This type of query is often referred to as an instant query. Instant queries are mostly used for metric type LogQL queries. -It accepts the following query parameters in the URL: +This type of query is often referred to as an instant query. Instant queries are only used for metric type LogQL queries +and will return a 400 (Bad Request) in case a log type query is provided. +The endpoint accepts the following query parameters in the URL: - `query`: The [LogQL]({{< relref "../query" >}}) query to perform. Requests that do not use valid LogQL syntax will return errors. - `limit`: The max number of entries to return. It defaults to `100`. Only applies to query types which produce a stream (log lines) response. diff --git a/integration/loki_micro_services_test.go b/integration/loki_micro_services_test.go index a048d0932494..48f9123d96eb 100644 --- a/integration/loki_micro_services_test.go +++ b/integration/loki_micro_services_test.go @@ -1045,7 +1045,7 @@ func TestCategorizedLabels(t *testing.T) { expectedEncodingFlags = tc.encodingFlags } - resp, err := cliQueryFrontend.RunQuery(context.Background(), tc.query, headers...) + resp, err := cliQueryFrontend.RunRangeQuery(context.Background(), tc.query, headers...) require.NoError(t, err) assert.Equal(t, "streams", resp.Data.ResultType) diff --git a/pkg/logqlmodel/error.go b/pkg/logqlmodel/error.go index 68ddf72cc2f2..e7265952ef0b 100644 --- a/pkg/logqlmodel/error.go +++ b/pkg/logqlmodel/error.go @@ -10,15 +10,16 @@ import ( // Those errors are useful for comparing error returned by the engine. // e.g. errors.Is(err,logqlmodel.ErrParse) let you know if this is a ast parsing error. var ( - ErrParse = errors.New("failed to parse the log query") - ErrPipeline = errors.New("failed execute pipeline") - ErrLimit = errors.New("limit reached while evaluating the query") - ErrIntervalLimit = errors.New("[interval] value exceeds limit") - ErrBlocked = errors.New("query blocked by policy") - ErrParseMatchers = errors.New("only label matchers are supported") - ErrorLabel = "__error__" - PreserveErrorLabel = "__preserve_error__" - ErrorDetailsLabel = "__error_details__" + ErrParse = errors.New("failed to parse the log query") + ErrPipeline = errors.New("failed execute pipeline") + ErrLimit = errors.New("limit reached while evaluating the query") + ErrIntervalLimit = errors.New("[interval] value exceeds limit") + ErrBlocked = errors.New("query blocked by policy") + ErrParseMatchers = errors.New("only label matchers are supported") + ErrUnsupportedSyntaxForInstantQuery = errors.New("log queries are not supported as an instant query type, please change your query to a range query type") + ErrorLabel = "__error__" + PreserveErrorLabel = "__preserve_error__" + ErrorDetailsLabel = "__error_details__" ) // ParseError is what is returned when we failed to parse. diff --git a/pkg/querier/http.go b/pkg/querier/http.go index 6996c29c65a6..cb80af31c5da 100644 --- a/pkg/querier/http.go +++ b/pkg/querier/http.go @@ -85,6 +85,11 @@ func (q *QuerierAPI) RangeQueryHandler(ctx context.Context, req *queryrange.Loki // InstantQueryHandler is a http.HandlerFunc for instant queries. func (q *QuerierAPI) InstantQueryHandler(ctx context.Context, req *queryrange.LokiInstantRequest) (logqlmodel.Result, error) { + // do not allow log selector expression (aka log query) as instant query + if _, ok := req.Plan.AST.(syntax.SampleExpr); !ok { + return logqlmodel.Result{}, logqlmodel.ErrUnsupportedSyntaxForInstantQuery + } + if err := q.validateMaxEntriesLimits(ctx, req.Plan.AST, req.Limit); err != nil { return logqlmodel.Result{}, err } diff --git a/pkg/querier/http_test.go b/pkg/querier/http_test.go index a97e55f882ba..d568b7b9934b 100644 --- a/pkg/querier/http_test.go +++ b/pkg/querier/http_test.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/loki/v3/pkg/loghttp" "github.com/grafana/loki/v3/pkg/logproto" + "github.com/grafana/loki/v3/pkg/logqlmodel" "github.com/grafana/loki/v3/pkg/validation" "github.com/go-kit/log" @@ -21,6 +22,35 @@ import ( "github.com/stretchr/testify/require" ) +func TestInstantQueryHandler(t *testing.T) { + defaultLimits := defaultLimitsTestConfig() + limits, err := validation.NewOverrides(defaultLimits, nil) + require.NoError(t, err) + + t.Run("log selector expression not allowed for instant queries", func(t *testing.T) { + api := NewQuerierAPI(mockQuerierConfig(), nil, limits, log.NewNopLogger()) + + ctx := user.InjectOrgID(context.Background(), "user") + req, err := http.NewRequestWithContext(ctx, "GET", `/api/v1/query`, nil) + require.NoError(t, err) + + q := req.URL.Query() + q.Add("query", `{app="loki"}`) + req.URL.RawQuery = q.Encode() + err = req.ParseForm() + require.NoError(t, err) + + rr := httptest.NewRecorder() + + handler := NewQuerierHandler(api) + httpHandler := NewQuerierHTTPHandler(handler) + + httpHandler.ServeHTTP(rr, req) + require.Equal(t, http.StatusBadRequest, rr.Code) + require.Equal(t, logqlmodel.ErrUnsupportedSyntaxForInstantQuery.Error(), rr.Body.String()) + }) +} + func TestTailHandler(t *testing.T) { defaultLimits := defaultLimitsTestConfig() limits, err := validation.NewOverrides(defaultLimits, nil) diff --git a/pkg/util/server/error.go b/pkg/util/server/error.go index 7326f7cecb6c..8ff7457a605b 100644 --- a/pkg/util/server/error.go +++ b/pkg/util/server/error.go @@ -78,7 +78,12 @@ func ClientHTTPStatusAndError(err error) (int, error) { return http.StatusGatewayTimeout, errors.New(ErrDeadlineExceeded) case errors.As(err, &queryErr): return http.StatusBadRequest, err - case errors.Is(err, logqlmodel.ErrLimit) || errors.Is(err, logqlmodel.ErrParse) || errors.Is(err, logqlmodel.ErrPipeline) || errors.Is(err, logqlmodel.ErrBlocked) || errors.Is(err, logqlmodel.ErrParseMatchers): + case errors.Is(err, logqlmodel.ErrLimit) || + errors.Is(err, logqlmodel.ErrParse) || + errors.Is(err, logqlmodel.ErrPipeline) || + errors.Is(err, logqlmodel.ErrBlocked) || + errors.Is(err, logqlmodel.ErrParseMatchers) || + errors.Is(err, logqlmodel.ErrUnsupportedSyntaxForInstantQuery): return http.StatusBadRequest, err case errors.Is(err, user.ErrNoOrgID): return http.StatusBadRequest, err