diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go index e002cff31..27484e899 100644 --- a/cmd/prometheus/main.go +++ b/cmd/prometheus/main.go @@ -748,6 +748,12 @@ func reloadConfig(filename string, logger log.Logger, rls ...func(*config.Config return errors.Errorf("one or more errors occurred while applying the new configuration (--config.file=%q)", filename) } promql.SetDefaultEvaluationInterval(time.Duration(conf.GlobalConfig.EvaluationInterval)) + + // Register conf as current config. + config.CurrentConfigMutex.Lock() + config.CurrentConfig = conf + config.CurrentConfigMutex.Unlock() + level.Info(logger).Log("msg", "Completed loading of configuration file", "filename", filename) return nil } diff --git a/config/config.go b/config/config.go index f4d8d470d..7acd54ad4 100644 --- a/config/config.go +++ b/config/config.go @@ -20,6 +20,7 @@ import ( "path/filepath" "regexp" "strings" + "sync" "time" "github.com/pkg/errors" @@ -68,6 +69,10 @@ func LoadFile(filename string) (*Config, error) { // The defaults applied before parsing the respective config sections. var ( + // CurrentConfig is a pointer to the current configuration. + CurrentConfig *Config + CurrentConfigMutex sync.RWMutex + // DefaultConfig is the default top-level configuration. DefaultConfig = Config{ GlobalConfig: DefaultGlobalConfig, diff --git a/docs/configuration/alerting_rules.md b/docs/configuration/alerting_rules.md index cc674a09b..e8110a1a8 100644 --- a/docs/configuration/alerting_rules.md +++ b/docs/configuration/alerting_rules.md @@ -43,7 +43,7 @@ The `annotations` clause specifies a set of informational labels that can be use #### Templating Label and annotation values can be templated using [console templates](https://prometheus.io/docs/visualization/consoles). -The `$labels` variable holds the label key/value pairs of an alert instance +The `$labels` & `$externalLabels` variables hold the label key/value pairs of an alert instance and `$value` holds the evaluated value of an alert instance. # To insert a firing element's label values: diff --git a/pkg/rulefmt/rulefmt.go b/pkg/rulefmt/rulefmt.go index 60faee085..19a2d1836 100644 --- a/pkg/rulefmt/rulefmt.go +++ b/pkg/rulefmt/rulefmt.go @@ -155,7 +155,7 @@ func testTemplateParsing(rl *Rule) (errs []error) { } // Trying to parse templates. - tmplData := template.AlertTemplateData(make(map[string]string), 0) + tmplData := template.AlertTemplateData(make(map[string]string), make(map[string]string), 0) defs := "{{$labels := .Labels}}{{$value := .Value}}" parseTest := func(text string) error { tmpl := template.NewTemplateExpander( diff --git a/rules/alerting.go b/rules/alerting.go index 7292d287b..c215fc014 100644 --- a/rules/alerting.go +++ b/rules/alerting.go @@ -28,6 +28,7 @@ import ( "github.com/go-kit/kit/log/level" "github.com/pkg/errors" "github.com/prometheus/common/model" + "github.com/prometheus/prometheus/config" "github.com/prometheus/prometheus/pkg/labels" "github.com/prometheus/prometheus/pkg/rulefmt" @@ -79,8 +80,9 @@ func (s AlertState) String() string { type Alert struct { State AlertState - Labels labels.Labels - Annotations labels.Labels + Labels labels.Labels + ExternalLabels labels.Labels + Annotations labels.Labels // The value at the last evaluation of the alerting expression. Value float64 @@ -300,15 +302,27 @@ func (r *AlertingRule) Eval(ctx context.Context, ts time.Time, query QueryFunc, var vec promql.Vector for _, smpl := range res { // Provide the alert information to the template. - l := make(map[string]string, len(smpl.Metric)) + l := make(map[string]string) for _, lbl := range smpl.Metric { l[lbl.Name] = lbl.Value } - tmplData := template.AlertTemplateData(l, smpl.V) + // Add external labels. + el := make(map[string]string) + if config.CurrentConfig != nil { + config.CurrentConfigMutex.RLock() + for eln, elv := range (*config.CurrentConfig).GlobalConfig.ExternalLabels { + el[string(eln)] = string(elv) + } + config.CurrentConfigMutex.RUnlock() + } + + tmplData := template.AlertTemplateData(l, el, smpl.V) // Inject some convenience variables that are easier to remember for users // who are not used to Go's templating system. - defs := "{{$labels := .Labels}}{{$value := .Value}}" + defs := "{{$labels := .Labels}}" + defs += "{{$externalLabels := .ExternalLabels}}" + defs += "{{$value := .Value}}" expand := func(text string) string { tmpl := template.NewTemplateExpander( diff --git a/template/template.go b/template/template.go index ef6212780..88b0e841d 100644 --- a/template/template.go +++ b/template/template.go @@ -28,9 +28,10 @@ import ( text_template "text/template" "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" - + "github.com/prometheus/prometheus/config" "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/util/strutil" ) @@ -133,6 +134,19 @@ func NewTemplateExpander( "label": func(label string, s *sample) string { return s.Labels[label] }, + "externalLabel": func(label string) string { + if config.CurrentConfig == nil { + return "" + } + config.CurrentConfigMutex.RLock() + defer config.CurrentConfigMutex.RUnlock() + for eln, elv := range (*config.CurrentConfig).GlobalConfig.ExternalLabels { + if label == string(eln) { + return string(elv) + } + } + return "" + }, "value": func(s *sample) float64 { return s.Value }, @@ -261,13 +275,15 @@ func NewTemplateExpander( } // AlertTemplateData returns the interface to be used in expanding the template. -func AlertTemplateData(labels map[string]string, value float64) interface{} { +func AlertTemplateData(labels map[string]string, externalLabels map[string]string, value float64) interface{} { return struct { - Labels map[string]string - Value float64 + Labels map[string]string + ExternalLabels map[string]string + Value float64 }{ - Labels: labels, - Value: value, + Labels: labels, + ExternalLabels: externalLabels, + Value: value, } } diff --git a/web/web.go b/web/web.go index 6a23e0254..4762bb6dd 100644 --- a/web/web.go +++ b/web/web.go @@ -541,19 +541,39 @@ func (h *Handler) consoles(w http.ResponseWriter, r *http.Request) { for k, v := range rawParams { params[k] = v[0] } + + // External labels + el := make(map[string]string) + if config.CurrentConfig != nil { + config.CurrentConfigMutex.RLock() + for eln, elv := range (*config.CurrentConfig).GlobalConfig.ExternalLabels { + el[string(eln)] = string(elv) + } + config.CurrentConfigMutex.RUnlock() + } + + // Inject some convenience variables that are easier to remember for users + // who are not used to Go's templating system. + defs := "{{$rawParams := .RawParams }}" + defs += "{{$params := .Params}}" + defs += "{{$path := .Path}}" + defs += "{{$externalLabels := .ExternalLabels}}" + data := struct { - RawParams url.Values - Params map[string]string - Path string + RawParams url.Values + Params map[string]string + Path string + ExternalLabels map[string]string }{ - RawParams: rawParams, - Params: params, - Path: strings.TrimLeft(name, "/"), + RawParams: rawParams, + Params: params, + Path: strings.TrimLeft(name, "/"), + ExternalLabels: el, } tmpl := template.NewTemplateExpander( h.context, - string(text), + defs+string(text), "__console_"+name, data, h.now(),