From 92869b510391be47d5948a06b948ec00b91ecdcb Mon Sep 17 00:00:00 2001 From: Zhuoyuan Liu Date: Thu, 20 Apr 2023 22:38:28 +0200 Subject: [PATCH 01/10] Add alert source template in rule Signed-off-by: Zhuoyuan Liu --- cmd/thanos/rule.go | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/cmd/thanos/rule.go b/cmd/thanos/rule.go index 6edf95416c..c0cb701651 100644 --- a/cmd/thanos/rule.go +++ b/cmd/thanos/rule.go @@ -4,6 +4,7 @@ package main import ( + "bytes" "context" "math/rand" "net/http" @@ -13,6 +14,7 @@ import ( "sort" "strconv" "strings" + "text/template" "time" extflag "github.com/efficientgo/tools/extkingpin" @@ -37,7 +39,6 @@ import ( "github.com/prometheus/prometheus/storage/remote" "github.com/prometheus/prometheus/tsdb" "github.com/prometheus/prometheus/tsdb/agent" - "github.com/prometheus/prometheus/util/strutil" "github.com/thanos-io/objstore/client" "gopkg.in/yaml.v2" @@ -95,6 +96,8 @@ type ruleConfig struct { lset labels.Labels ignoredLabelNames []string storeRateLimits store.SeriesSelectLimits + + alertSourceTemplate string } func (rc *ruleConfig) registerFlag(cmd extkingpin.FlagClause) { @@ -124,6 +127,7 @@ func registerRule(app *extkingpin.App) { noLockFile := cmd.Flag("tsdb.no-lockfile", "Do not create lockfile in TSDB data directory. In any case, the lockfiles will be deleted on next startup.").Default("false").Bool() walCompression := cmd.Flag("tsdb.wal-compression", "Compress the tsdb WAL.").Default("true").Bool() + cmd.Flag("alert-source-template", "Template to use in alerts source field. Need only include {{.Expr}} parameter").Default("/graph?g0.expr={{.Expr}}&g0.tab=1").StringVar(&conf.alertSourceTemplate) cmd.Flag("data-dir", "data directory").Default("data/").StringVar(&conf.dataDir) cmd.Flag("rule-file", "Rule files that should be used by rule manager. Can be in glob format (repeated). Note that rules are not automatically detected, use SIGHUP or do HTTP POST /-/reload to re-read them."). Default("rules/").StringsVar(&conf.ruleFiles) @@ -494,7 +498,7 @@ func runRule( StartsAt: alrt.FiredAt, Labels: alrt.Labels, Annotations: alrt.Annotations, - GeneratorURL: conf.alertQueryURL.String() + strutil.TableLinkForExpression(expr), + GeneratorURL: conf.alertQueryURL.String() + TableLinkForExpression(expr, conf.alertSourceTemplate), } if !alrt.ResolvedAt.IsZero() { a.EndsAt = alrt.ResolvedAt @@ -913,3 +917,23 @@ func reloadRules(logger log.Logger, } return errs.Err() } + +func TableLinkForExpression(expr string, tmpl string) string { + // template example: "/graph?g0.expr={{.Expr}}&g0.tab=1" + escapedExpression := url.QueryEscape(expr) + + type expression struct { + Expr string + } + escapedExpr := expression{Expr: escapedExpression} + t, err := template.New("url").Parse(tmpl) + if err != nil { + panic(err) + } + var buf bytes.Buffer + + if err := t.Execute(&buf, escapedExpr); err != nil { + panic(err) + } + return buf.String() +} From 5145d869c75deb5353f7e45bff3373f3b154e0b6 Mon Sep 17 00:00:00 2001 From: Zhuoyuan Liu Date: Fri, 21 Apr 2023 16:59:06 +0200 Subject: [PATCH 02/10] Validate template in start phase Signed-off-by: Zhuoyuan Liu --- cmd/thanos/rule.go | 50 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/cmd/thanos/rule.go b/cmd/thanos/rule.go index c0cb701651..d392dbd0c4 100644 --- a/cmd/thanos/rule.go +++ b/cmd/thanos/rule.go @@ -6,6 +6,8 @@ package main import ( "bytes" "context" + "fmt" + "html/template" "math/rand" "net/http" "net/url" @@ -14,7 +16,6 @@ import ( "sort" "strconv" "strings" - "text/template" "time" extflag "github.com/efficientgo/tools/extkingpin" @@ -100,6 +101,10 @@ type ruleConfig struct { alertSourceTemplate string } +type Expression struct { + Expr string +} + func (rc *ruleConfig) registerFlag(cmd extkingpin.FlagClause) { rc.http.registerFlag(cmd) rc.grpc.registerFlag(cmd) @@ -127,7 +132,6 @@ func registerRule(app *extkingpin.App) { noLockFile := cmd.Flag("tsdb.no-lockfile", "Do not create lockfile in TSDB data directory. In any case, the lockfiles will be deleted on next startup.").Default("false").Bool() walCompression := cmd.Flag("tsdb.wal-compression", "Compress the tsdb WAL.").Default("true").Bool() - cmd.Flag("alert-source-template", "Template to use in alerts source field. Need only include {{.Expr}} parameter").Default("/graph?g0.expr={{.Expr}}&g0.tab=1").StringVar(&conf.alertSourceTemplate) cmd.Flag("data-dir", "data directory").Default("data/").StringVar(&conf.dataDir) cmd.Flag("rule-file", "Rule files that should be used by rule manager. Can be in glob format (repeated). Note that rules are not automatically detected, use SIGHUP or do HTTP POST /-/reload to re-read them."). Default("rules/").StringsVar(&conf.ruleFiles) @@ -141,6 +145,14 @@ func registerRule(app *extkingpin.App) { Default("10m").DurationVar(&conf.forGracePeriod) cmd.Flag("restore-ignored-label", "Label names to be ignored when restoring alerts from the remote storage. This is only used in stateless mode."). StringsVar(&conf.ignoredLabelNames) + cmd.Flag("alert-source-template", "Template to use in alerts source field. Need only include {{.Expr}} parameter").Default("/graph?g0.expr={{.Expr}}&g0.tab=1").StringVar(&conf.alertSourceTemplate) + + // validate the user provided template is valid + // if not, use the default template + if err := validateTemplate(conf.alertSourceTemplate, Expression{Expr: "test_expr"}); err != nil { + conf.alertSourceTemplate = "/graph?g0.expr={{.Expr}}&g0.tab=1" + // log the error + } conf.rwConfig = extflag.RegisterPathOrContent(cmd, "remote-write.config", "YAML config for the remote-write configurations, that specify servers where samples should be sent to (see https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write). This automatically enables stateless mode for ruler and no series will be stored in the ruler's TSDB. If an empty config (or file) is provided, the flag is ignored and ruler is run with its own TSDB.", extflag.WithEnvSubstitution()) @@ -494,11 +506,15 @@ func runRule( if alrt.State == rules.StatePending { continue } + expressionURL, err := TableLinkForExpression(expr, conf.alertSourceTemplate) + if err != nil { + level.Warn(logger).Log("msg", "failed to generate link for expression", "expr", expr, "err", err) + } a := ¬ifier.Alert{ StartsAt: alrt.FiredAt, Labels: alrt.Labels, Annotations: alrt.Annotations, - GeneratorURL: conf.alertQueryURL.String() + TableLinkForExpression(expr, conf.alertSourceTemplate), + GeneratorURL: conf.alertQueryURL.String() + expressionURL, } if !alrt.ResolvedAt.IsZero() { a.EndsAt = alrt.ResolvedAt @@ -918,22 +934,34 @@ func reloadRules(logger log.Logger, return errs.Err() } -func TableLinkForExpression(expr string, tmpl string) string { +func TableLinkForExpression(expr string, tmpl string) (string, error) { // template example: "/graph?g0.expr={{.Expr}}&g0.tab=1" escapedExpression := url.QueryEscape(expr) - type expression struct { - Expr string - } - escapedExpr := expression{Expr: escapedExpression} + escapedExpr := Expression{Expr: escapedExpression} t, err := template.New("url").Parse(tmpl) if err != nil { - panic(err) + return "", errors.Wrap(err, "failed to parse template") } var buf bytes.Buffer if err := t.Execute(&buf, escapedExpr); err != nil { - panic(err) + return "", errors.Wrap(err, "failed to execute template") + } + return buf.String(), nil +} + +func validateTemplate(tmplStr string, data Expression) error { + tmpl, err := template.New("test").Parse(tmplStr) + if err != nil { + return fmt.Errorf("failed to parse the template: %w", err) } - return buf.String() + + var buf bytes.Buffer + err = tmpl.Execute(&buf, data) + if err != nil { + return fmt.Errorf("failed to execute the template: %w", err) + } + + return nil } From 9db713b0fa5895871e61e6ed71b319775b094dc0 Mon Sep 17 00:00:00 2001 From: Zhuoyuan Liu Date: Fri, 21 Apr 2023 17:06:02 +0200 Subject: [PATCH 03/10] Move the start check to runrule Signed-off-by: Zhuoyuan Liu --- cmd/thanos/rule.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/cmd/thanos/rule.go b/cmd/thanos/rule.go index d392dbd0c4..9bbf2a531c 100644 --- a/cmd/thanos/rule.go +++ b/cmd/thanos/rule.go @@ -147,13 +147,6 @@ func registerRule(app *extkingpin.App) { StringsVar(&conf.ignoredLabelNames) cmd.Flag("alert-source-template", "Template to use in alerts source field. Need only include {{.Expr}} parameter").Default("/graph?g0.expr={{.Expr}}&g0.tab=1").StringVar(&conf.alertSourceTemplate) - // validate the user provided template is valid - // if not, use the default template - if err := validateTemplate(conf.alertSourceTemplate, Expression{Expr: "test_expr"}); err != nil { - conf.alertSourceTemplate = "/graph?g0.expr={{.Expr}}&g0.tab=1" - // log the error - } - conf.rwConfig = extflag.RegisterPathOrContent(cmd, "remote-write.config", "YAML config for the remote-write configurations, that specify servers where samples should be sent to (see https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write). This automatically enables stateless mode for ruler and no series will be stored in the ruler's TSDB. If an empty config (or file) is provided, the flag is ignored and ruler is run with its own TSDB.", extflag.WithEnvSubstitution()) reqLogDecision := cmd.Flag("log.request.decision", "Deprecation Warning - This flag would be soon deprecated, and replaced with `request.logging-config`. Request Logging for logging the start and end of requests. By default this flag is disabled. LogFinishCall: Logs the finish call of the requests. LogStartAndFinishCall: Logs the start and finish call of the requests. NoLogCall: Disable request logging.").Default("").Enum("NoLogCall", "LogFinishCall", "LogStartAndFinishCall", "") @@ -343,6 +336,10 @@ func runRule( } } + if err := validateTemplate(conf.alertSourceTemplate, Expression{Expr: "test_expr"}); err != nil { + return errors.Wrap(err, "invalid alert source template") + } + queryProvider := dns.NewProvider( logger, extprom.WrapRegistererWithPrefix("thanos_rule_query_apis_", reg), From 2ef23d75a6be0952a35b427c2016d803bcc073ca Mon Sep 17 00:00:00 2001 From: Zhuoyuan Liu Date: Wed, 26 Apr 2023 13:30:43 +0200 Subject: [PATCH 04/10] move the flag to config.go Signed-off-by: Zhuoyuan Liu --- cmd/thanos/config.go | 3 +++ cmd/thanos/rule.go | 10 +++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cmd/thanos/config.go b/cmd/thanos/config.go index 4986de9af8..1ccfe7b960 100644 --- a/cmd/thanos/config.go +++ b/cmd/thanos/config.go @@ -217,6 +217,7 @@ type alertMgrConfig struct { alertExcludeLabels []string alertQueryURL *string alertRelabelConfigPath *extflag.PathOrContent + alertSourceTemplate *string } func (ac *alertMgrConfig) registerFlag(cmd extflag.FlagClause) *alertMgrConfig { @@ -231,5 +232,7 @@ func (ac *alertMgrConfig) registerFlag(cmd extflag.FlagClause) *alertMgrConfig { cmd.Flag("alert.label-drop", "Labels by name to drop before sending to alertmanager. This allows alert to be deduplicated on replica label (repeated). Similar Prometheus alert relabelling"). StringsVar(&ac.alertExcludeLabels) ac.alertRelabelConfigPath = extflag.RegisterPathOrContent(cmd, "alert.relabel-config", "YAML file that contains alert relabelling configuration.", extflag.WithEnvSubstitution()) + ac.alertSourceTemplate = cmd.Flag("alert.query-template", "Template to use in alerts source field. Need only include {{.Expr}} parameter").Default("/graph?g0.expr={{.Expr}}&g0.tab=1").String() + return ac } diff --git a/cmd/thanos/rule.go b/cmd/thanos/rule.go index 9bbf2a531c..621573d541 100644 --- a/cmd/thanos/rule.go +++ b/cmd/thanos/rule.go @@ -97,8 +97,6 @@ type ruleConfig struct { lset labels.Labels ignoredLabelNames []string storeRateLimits store.SeriesSelectLimits - - alertSourceTemplate string } type Expression struct { @@ -145,7 +143,6 @@ func registerRule(app *extkingpin.App) { Default("10m").DurationVar(&conf.forGracePeriod) cmd.Flag("restore-ignored-label", "Label names to be ignored when restoring alerts from the remote storage. This is only used in stateless mode."). StringsVar(&conf.ignoredLabelNames) - cmd.Flag("alert-source-template", "Template to use in alerts source field. Need only include {{.Expr}} parameter").Default("/graph?g0.expr={{.Expr}}&g0.tab=1").StringVar(&conf.alertSourceTemplate) conf.rwConfig = extflag.RegisterPathOrContent(cmd, "remote-write.config", "YAML config for the remote-write configurations, that specify servers where samples should be sent to (see https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write). This automatically enables stateless mode for ruler and no series will be stored in the ruler's TSDB. If an empty config (or file) is provided, the flag is ignored and ruler is run with its own TSDB.", extflag.WithEnvSubstitution()) @@ -336,7 +333,7 @@ func runRule( } } - if err := validateTemplate(conf.alertSourceTemplate, Expression{Expr: "test_expr"}); err != nil { + if err := validateTemplate(*conf.alertmgr.alertSourceTemplate, Expression{Expr: "test_expr"}); err != nil { return errors.Wrap(err, "invalid alert source template") } @@ -503,7 +500,7 @@ func runRule( if alrt.State == rules.StatePending { continue } - expressionURL, err := TableLinkForExpression(expr, conf.alertSourceTemplate) + expressionURL, err := TableLinkForExpression(expr, *conf.alertmgr.alertSourceTemplate) if err != nil { level.Warn(logger).Log("msg", "failed to generate link for expression", "expr", expr, "err", err) } @@ -940,8 +937,8 @@ func TableLinkForExpression(expr string, tmpl string) (string, error) { if err != nil { return "", errors.Wrap(err, "failed to parse template") } - var buf bytes.Buffer + var buf bytes.Buffer if err := t.Execute(&buf, escapedExpr); err != nil { return "", errors.Wrap(err, "failed to execute template") } @@ -959,6 +956,5 @@ func validateTemplate(tmplStr string, data Expression) error { if err != nil { return fmt.Errorf("failed to execute the template: %w", err) } - return nil } From 4b4f21a20c4132a52fe35a752e4bda2999ec5117 Mon Sep 17 00:00:00 2001 From: Zhuoyuan Liu Date: Wed, 26 Apr 2023 13:33:28 +0200 Subject: [PATCH 05/10] Updates the docs Signed-off-by: Zhuoyuan Liu --- docs/components/rule.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/components/rule.md b/docs/components/rule.md index 93e5e91cfe..725d16e206 100644 --- a/docs/components/rule.md +++ b/docs/components/rule.md @@ -267,6 +267,9 @@ Flags: to alertmanager. This allows alert to be deduplicated on replica label (repeated). Similar Prometheus alert relabelling + --alert.query-template="/graph?g0.expr={{.Expr}}&g0.tab=1" + Template to use in alerts source field. + Need only include {{.Expr}} parameter --alert.query-url=ALERT.QUERY-URL The external Thanos Query URL that would be set in all alerts 'Source' field From c854f9ad22c1f31f6838c25b23fd8349eac62afe Mon Sep 17 00:00:00 2001 From: Zhuoyuan Liu Date: Thu, 24 Aug 2023 13:11:43 +0200 Subject: [PATCH 06/10] Add test for validateTemplate Signed-off-by: Zhuoyuan Liu --- cmd/thanos/rule_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/cmd/thanos/rule_test.go b/cmd/thanos/rule_test.go index 5703c7b3cc..bae5923869 100644 --- a/cmd/thanos/rule_test.go +++ b/cmd/thanos/rule_test.go @@ -48,3 +48,27 @@ func Test_parseFlagLabels(t *testing.T) { testutil.Equals(t, err != nil, td.expectErr) } } + +func Test_validateTemplate(t *testing.T) { + tData := []struct { + template string + expectErr bool + }{ + { + template: `/graph?g0.expr={{.Expr}}&g0.tab=1`, + expectErr: false, + }, + { + template: `/graph?g0.expr={{.Expression}}&g0.tab=1`, + expectErr: true, + }, + { + template: `another template includes {{.Expr}}`, + expectErr: false, + }, + } + for _, td := range tData { + err := validateTemplate(td.template, Expression{Expr: "test_expr"}) + testutil.Equals(t, err != nil, td.expectErr) + } +} From b8a4a9c2548133e69e9040b96a3e016b4884d4d9 Mon Sep 17 00:00:00 2001 From: Zhuoyuan Liu Date: Tue, 5 Sep 2023 14:29:33 +0200 Subject: [PATCH 07/10] Add new test case Signed-off-by: Zhuoyuan Liu --- cmd/thanos/rule.go | 4 ++-- cmd/thanos/rule_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/cmd/thanos/rule.go b/cmd/thanos/rule.go index f489999e6c..b30f226b50 100644 --- a/cmd/thanos/rule.go +++ b/cmd/thanos/rule.go @@ -502,7 +502,7 @@ func runRule( if alrt.State == rules.StatePending { continue } - expressionURL, err := TableLinkForExpression(expr, *conf.alertmgr.alertSourceTemplate) + expressionURL, err := tableLinkForExpression(*conf.alertmgr.alertSourceTemplate, expr) if err != nil { level.Warn(logger).Log("msg", "failed to generate link for expression", "expr", expr, "err", err) } @@ -949,7 +949,7 @@ func reloadRules(logger log.Logger, return errs.Err() } -func TableLinkForExpression(expr string, tmpl string) (string, error) { +func tableLinkForExpression(tmpl string, expr string) (string, error) { // template example: "/graph?g0.expr={{.Expr}}&g0.tab=1" escapedExpression := url.QueryEscape(expr) diff --git a/cmd/thanos/rule_test.go b/cmd/thanos/rule_test.go index bae5923869..c9ef63cdad 100644 --- a/cmd/thanos/rule_test.go +++ b/cmd/thanos/rule_test.go @@ -72,3 +72,35 @@ func Test_validateTemplate(t *testing.T) { testutil.Equals(t, err != nil, td.expectErr) } } + +func Test_tableLinkForExpression(t *testing.T) { + tData := []struct { + template string + expr string + expectStr string + expectErr bool + }{ + { + template: `/graph?g0.expr={{.Expr}}&g0.tab=1`, + expr: `up{app="foo"}`, + expectStr: `/graph?g0.expr=up%7Bapp%3D%22foo%22%7D&g0.tab=1`, + expectErr: false, + }, + { + template: `/graph?g0.expr={{.Expression}}&g0.tab=1`, + expr: "test_expr", + expectErr: true, + }, + { + template: `another template includes {{.Expr}}`, + expr: "test_expr", + expectStr: `another template includes test_expr`, + expectErr: false, + }, + } + for _, td := range tData { + resStr, err := tableLinkForExpression(td.template, td.expr) + testutil.Equals(t, err != nil, td.expectErr) + testutil.Equals(t, resStr, td.expectStr) + } +} From bf75b89f07a366bb95e5fb2158da9cb884f42cdd Mon Sep 17 00:00:00 2001 From: Zhuoyuan Liu Date: Tue, 12 Sep 2023 12:09:16 +0200 Subject: [PATCH 08/10] Remove unnecessary variable Signed-off-by: Zhuoyuan Liu --- cmd/thanos/rule.go | 7 +++---- cmd/thanos/rule_test.go | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cmd/thanos/rule.go b/cmd/thanos/rule.go index b30f226b50..2d519185a3 100644 --- a/cmd/thanos/rule.go +++ b/cmd/thanos/rule.go @@ -335,7 +335,7 @@ func runRule( } } - if err := validateTemplate(*conf.alertmgr.alertSourceTemplate, Expression{Expr: "test_expr"}); err != nil { + if err := validateTemplate(*conf.alertmgr.alertSourceTemplate); err != nil { return errors.Wrap(err, "invalid alert source template") } @@ -966,14 +966,13 @@ func tableLinkForExpression(tmpl string, expr string) (string, error) { return buf.String(), nil } -func validateTemplate(tmplStr string, data Expression) error { +func validateTemplate(tmplStr string) error { tmpl, err := template.New("test").Parse(tmplStr) if err != nil { return fmt.Errorf("failed to parse the template: %w", err) } - var buf bytes.Buffer - err = tmpl.Execute(&buf, data) + err = tmpl.Execute(&buf, Expression{Expr: "test_expr"}) if err != nil { return fmt.Errorf("failed to execute the template: %w", err) } diff --git a/cmd/thanos/rule_test.go b/cmd/thanos/rule_test.go index c9ef63cdad..3ba4d65b25 100644 --- a/cmd/thanos/rule_test.go +++ b/cmd/thanos/rule_test.go @@ -68,7 +68,7 @@ func Test_validateTemplate(t *testing.T) { }, } for _, td := range tData { - err := validateTemplate(td.template, Expression{Expr: "test_expr"}) + err := validateTemplate(td.template) testutil.Equals(t, err != nil, td.expectErr) } } From 16e35662fc5d969b152493edfb1305cf3170e703 Mon Sep 17 00:00:00 2001 From: Zhuoyuan Liu Date: Tue, 12 Sep 2023 12:15:03 +0200 Subject: [PATCH 09/10] Add changelogs Signed-off-by: Zhuoyuan Liu --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 284d0a340a..9fa0c83e72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ We use *breaking :warning:* to mark changes that are not backward compatible (re ### Added - [#6605](https://github.com/thanos-io/thanos/pull/6605) Query Frontend: Support vertical sharding binary expression with metric name when no matching labels specified. - +- [#6308](https://github.com/thanos-io/thanos/pull/6308) Ruler: Support configuration flag that allows customizing template for alert message. ### Changed - [#6664](https://github.com/thanos-io/thanos/pull/6664) *: Update Prometheus to 2.46.1. From 0c99f0e01a07f08af21690f11f6db7b904cfadc1 Mon Sep 17 00:00:00 2001 From: Matej Gera <38492574+matej-g@users.noreply.github.com> Date: Wed, 13 Sep 2023 15:05:14 +0200 Subject: [PATCH 10/10] Update CHANGELOG.md Signed-off-by: Matej Gera <38492574+matej-g@users.noreply.github.com> --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fa0c83e72..a5ac24d927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ We use *breaking :warning:* to mark changes that are not backward compatible (re - [#6605](https://github.com/thanos-io/thanos/pull/6605) Query Frontend: Support vertical sharding binary expression with metric name when no matching labels specified. - [#6308](https://github.com/thanos-io/thanos/pull/6308) Ruler: Support configuration flag that allows customizing template for alert message. + ### Changed - [#6664](https://github.com/thanos-io/thanos/pull/6664) *: Update Prometheus to 2.46.1.