From 98417a579f470f25287b4e193c99824435f2f22c Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Mon, 15 Apr 2024 16:30:09 +0100 Subject: [PATCH] [v11.0.x] Loki: Support custom `X-Query-Tags` header (#86189) Loki: Support custom `X-Query-Tags` header (#85123) * Loki: Support custom `X-Query-Tags` header * add comment (cherry picked from commit 9a8ae3c93204d54e3355300479fe6adbe1a6e5c1) Co-authored-by: Sven Grossmann --- pkg/tsdb/loki/api.go | 12 +++-- pkg/tsdb/loki/api_test.go | 14 +++++- pkg/tsdb/loki/parse_query.go | 40 ++++++++-------- pkg/tsdb/loki/parse_query_test.go | 79 +++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 24 deletions(-) diff --git a/pkg/tsdb/loki/api.go b/pkg/tsdb/loki/api.go index 83f8c1919ea..6ffa5226a65 100644 --- a/pkg/tsdb/loki/api.go +++ b/pkg/tsdb/loki/api.go @@ -99,7 +99,7 @@ func makeDataRequest(ctx context.Context, lokiDsUrl string, query lokiQuery, cat } if query.SupportingQueryType != SupportingQueryNone { - value := getSupportingQueryHeaderValue(req, query.SupportingQueryType) + value := getSupportingQueryHeaderValue(query.SupportingQueryType) if value != "" { req.Header.Set("X-Query-Tags", "Source="+value) } @@ -324,8 +324,13 @@ func (api *LokiAPI) RawQuery(ctx context.Context, resourcePath string) (RawLokiR return rawLokiResponse, nil } -func getSupportingQueryHeaderValue(req *http.Request, supportingQueryType SupportingQueryType) string { +func getSupportingQueryHeaderValue(supportingQueryType SupportingQueryType) string { value := "" + + // we need to map the SupportingQueryType to the actual header value. For + // legacy reasons we defined each value, such as "logsVolume" maps to the + // "logvolhist" header value to Loki. With #85123, even the value set in the + // frontend query can be passed as is to Loki. switch supportingQueryType { case SupportingQueryLogsVolume: value = "logvolhist" @@ -335,7 +340,8 @@ func getSupportingQueryHeaderValue(req *http.Request, supportingQueryType Suppor value = "datasample" case SupportingQueryInfiniteScroll: value = "infinitescroll" - default: // ignore + default: + value = string(supportingQueryType) } return value diff --git a/pkg/tsdb/loki/api_test.go b/pkg/tsdb/loki/api_test.go index c510fd78d01..d7acd1c791d 100644 --- a/pkg/tsdb/loki/api_test.go +++ b/pkg/tsdb/loki/api_test.go @@ -58,7 +58,7 @@ func TestApiLogVolume(t *testing.T) { require.True(t, called) }) - t.Run("non-log-volume queries should not set log-volume http header", func(t *testing.T) { + t.Run("none queries should not set X-Query-Tags http header", func(t *testing.T) { called := false api := makeMockedAPI(200, "application/json", response, func(req *http.Request) { called = true @@ -70,6 +70,18 @@ func TestApiLogVolume(t *testing.T) { require.True(t, called) }) + t.Run("any defined supporting query should not set X-Query-Tags http header", func(t *testing.T) { + called := false + api := makeMockedAPI(200, "application/json", response, func(req *http.Request) { + called = true + require.Equal(t, "Source=foo", req.Header.Get("X-Query-Tags")) + }, false) + + _, err := api.DataQuery(context.Background(), lokiQuery{Expr: "", SupportingQueryType: SupportingQueryType("foo"), QueryType: QueryTypeRange}, ResponseOpts{}) + require.NoError(t, err) + require.True(t, called) + }) + t.Run("with `structuredMetadata` should set correct http header", func(t *testing.T) { called := false api := makeMockedAPI(200, "application/json", response, func(req *http.Request) { diff --git a/pkg/tsdb/loki/parse_query.go b/pkg/tsdb/loki/parse_query.go index 950e360fd85..a4f39679d6c 100644 --- a/pkg/tsdb/loki/parse_query.go +++ b/pkg/tsdb/loki/parse_query.go @@ -100,23 +100,26 @@ func parseDirection(jsonPointerValue *string) (Direction, error) { } } -func parseSupportingQueryType(jsonPointerValue *string) (SupportingQueryType, error) { +func parseSupportingQueryType(jsonPointerValue *string) SupportingQueryType { if jsonPointerValue == nil { - return SupportingQueryNone, nil - } else { - jsonValue := *jsonPointerValue - switch jsonValue { - case "logsVolume": - return SupportingQueryLogsVolume, nil - case "logsSample": - return SupportingQueryLogsSample, nil - case "dataSample": - return SupportingQueryDataSample, nil - case "infiniteScroll": - return SupportingQueryInfiniteScroll, nil - default: - return SupportingQueryNone, fmt.Errorf("invalid supportingQueryType: %s", jsonValue) - } + return SupportingQueryNone + } + + jsonValue := *jsonPointerValue + switch jsonValue { + case "logsVolume": + return SupportingQueryLogsVolume + case "logsSample": + return SupportingQueryLogsSample + case "dataSample": + return SupportingQueryDataSample + case "infiniteScroll": + return SupportingQueryInfiniteScroll + case "": + return SupportingQueryNone + default: + // `SupportingQueryType` is just a `string` in the schema, so we can just parse this as a string + return SupportingQueryType(jsonValue) } } @@ -166,10 +169,7 @@ func parseQuery(queryContext *backend.QueryDataRequest) ([]*lokiQuery, error) { legendFormat = *model.LegendFormat } - supportingQueryType, err := parseSupportingQueryType(model.SupportingQueryType) - if err != nil { - return nil, err - } + supportingQueryType := parseSupportingQueryType(model.SupportingQueryType) qs = append(qs, &lokiQuery{ Expr: expr, diff --git a/pkg/tsdb/loki/parse_query_test.go b/pkg/tsdb/loki/parse_query_test.go index 40dc6b9d5ca..50a111c489a 100644 --- a/pkg/tsdb/loki/parse_query_test.go +++ b/pkg/tsdb/loki/parse_query_test.go @@ -35,6 +35,85 @@ func TestParseQuery(t *testing.T) { require.Equal(t, time.Second*15, models[0].Step) require.Equal(t, "go_goroutines 15s 15000 3000s 3000 3000000", models[0].Expr) }) + + t.Run("parsing query model with logsVolume supporting query type", func(t *testing.T) { + queryContext := &backend.QueryDataRequest{ + Queries: []backend.DataQuery{ + { + JSON: []byte(` + { + "expr": "go_goroutines $__interval $__interval_ms $__range $__range_s $__range_ms", + "format": "time_series", + "refId": "A", + "supportingQueryType": "logsVolume" + }`, + ), + TimeRange: backend.TimeRange{ + From: time.Now().Add(-3000 * time.Second), + To: time.Now(), + }, + Interval: time.Second * 15, + MaxDataPoints: 200, + }, + }, + } + models, err := parseQuery(queryContext) + require.NoError(t, err) + require.Equal(t, SupportingQueryLogsVolume, models[0].SupportingQueryType) + }) + + t.Run("parsing query model with any supporting query type", func(t *testing.T) { + queryContext := &backend.QueryDataRequest{ + Queries: []backend.DataQuery{ + { + JSON: []byte(` + { + "expr": "go_goroutines $__interval $__interval_ms $__range $__range_s $__range_ms", + "format": "time_series", + "refId": "A", + "supportingQueryType": "foo" + }`, + ), + TimeRange: backend.TimeRange{ + From: time.Now().Add(-3000 * time.Second), + To: time.Now(), + }, + Interval: time.Second * 15, + MaxDataPoints: 200, + }, + }, + } + models, err := parseQuery(queryContext) + require.NoError(t, err) + require.Equal(t, SupportingQueryType("foo"), models[0].SupportingQueryType) + }) + + t.Run("parsing query model with any empty query type", func(t *testing.T) { + queryContext := &backend.QueryDataRequest{ + Queries: []backend.DataQuery{ + { + JSON: []byte(` + { + "expr": "go_goroutines $__interval $__interval_ms $__range $__range_s $__range_ms", + "format": "time_series", + "refId": "A", + "supportingQueryType": "" + }`, + ), + TimeRange: backend.TimeRange{ + From: time.Now().Add(-3000 * time.Second), + To: time.Now(), + }, + Interval: time.Second * 15, + MaxDataPoints: 200, + }, + }, + } + models, err := parseQuery(queryContext) + require.NoError(t, err) + require.Equal(t, SupportingQueryNone, models[0].SupportingQueryType) + }) + t.Run("interpolate variables, range between 1s and 0.5s", func(t *testing.T) { expr := "go_goroutines $__interval $__interval_ms $__range $__range_s $__range_ms" queryType := dataquery.LokiQueryTypeRange