diff --git a/pkg/services/ngalert/state/historian/core.go b/pkg/services/ngalert/state/historian/core.go index 5fff26fdfd8..f0eb45fe43f 100644 --- a/pkg/services/ngalert/state/historian/core.go +++ b/pkg/services/ngalert/state/historian/core.go @@ -1,10 +1,12 @@ package historian import ( + "fmt" "strings" "time" "github.com/grafana/grafana-plugin-sdk-go/data" + prometheus "github.com/prometheus/common/model" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/ngalert/eval" @@ -42,6 +44,12 @@ func removePrivateLabels(labels data.Labels) data.Labels { return result } +// labelFingerprint calculates a stable Prometheus-style signature for a label set. +func labelFingerprint(labels data.Labels) string { + sig := prometheus.LabelsToSignature(labels) + return fmt.Sprintf("%016x", sig) +} + // panelKey uniquely identifies a panel. type panelKey struct { orgID int64 diff --git a/pkg/services/ngalert/state/historian/loki.go b/pkg/services/ngalert/state/historian/loki.go index 1f8ff0e8196..b4610033879 100644 --- a/pkg/services/ngalert/state/historian/loki.go +++ b/pkg/services/ngalert/state/historian/loki.go @@ -255,6 +255,7 @@ func statesToStream(rule history_model.RuleMeta, states []state.StateTransition, continue } + sanitizedLabels := removePrivateLabels(state.Labels) entry := lokiEntry{ SchemaVersion: 1, Previous: state.PreviousFormatted(), @@ -263,7 +264,8 @@ func statesToStream(rule history_model.RuleMeta, states []state.StateTransition, Condition: rule.Condition, DashboardUID: rule.DashboardUID, PanelID: rule.PanelID, - InstanceLabels: removePrivateLabels(state.Labels), + Fingerprint: labelFingerprint(sanitizedLabels), + InstanceLabels: sanitizedLabels, } if state.State.State == eval.Error { entry.Error = state.Error.Error() @@ -306,6 +308,7 @@ type lokiEntry struct { Condition string `json:"condition"` DashboardUID string `json:"dashboardUID"` PanelID int64 `json:"panelID"` + Fingerprint string `json:"fingerprint"` // InstanceLabels is exactly the set of labels associated with the alert instance in Alertmanager. // These should not be conflated with labels associated with log streams. InstanceLabels map[string]string `json:"labels"` diff --git a/pkg/services/ngalert/state/historian/loki_test.go b/pkg/services/ngalert/state/historian/loki_test.go index b2c6a9b5ece..02b084b519d 100644 --- a/pkg/services/ngalert/state/historian/loki_test.go +++ b/pkg/services/ngalert/state/historian/loki_test.go @@ -154,6 +154,25 @@ func TestRemoteLokiBackend(t *testing.T) { entry := requireSingleEntry(t, res) require.Equal(t, rule.Condition, entry.Condition) }) + + t.Run("stores fingerprint of instance labels", func(t *testing.T) { + rule := createTestRule() + l := log.NewNopLogger() + states := singleFromNormal(&state.State{ + State: eval.Alerting, + Labels: data.Labels{ + "statelabel": "labelvalue", + "labeltwo": "labelvalue", + "labelthree": "labelvalue", + }, + }) + + res := statesToStream(rule, states, nil, l) + + entry := requireSingleEntry(t, res) + exp := labelFingerprint(states[0].Labels) + require.Equal(t, exp, entry.Fingerprint) + }) }) t.Run("selector string", func(t *testing.T) {