diff --git a/README.md b/README.md index 402bb3608..6fa4be97c 100644 --- a/README.md +++ b/README.md @@ -281,6 +281,21 @@ This option can also be set using `-jira.regex` flag. Example: This variable is optional and default is not set (no rule will be applied). +#### KEEP_LABELS + +List of label names to show on the UI, all other labels will be stripped. +This allows to hide all labels except selected few that are useful on the +alert dashboard. Accepts space separated list of label names. Examples: + + KEEP_LABELS=instance + KEEP_LABELS="host severity" + +This option can also be set using `-keep.labels` flag. Example: + + $ unsee -keep.labels "host severity" + +This variable is optional and default is not set (all labels will be shown). + #### PORT HTTP port to listen on. Example: diff --git a/internal/alertmanager/dedup.go b/internal/alertmanager/dedup.go index b20a1545a..8b2c55b32 100644 --- a/internal/alertmanager/dedup.go +++ b/internal/alertmanager/dedup.go @@ -3,8 +3,10 @@ package alertmanager import ( "sort" + "github.com/cloudflare/unsee/internal/config" "github.com/cloudflare/unsee/internal/models" "github.com/cloudflare/unsee/internal/slices" + "github.com/cloudflare/unsee/internal/transform" ) // DedupAlerts will collect alert groups from all defined Alertmanager @@ -61,6 +63,8 @@ func DedupAlerts() []models.AlertGroup { ag := models.AlertGroup(agList[0]) ag.Alerts = models.AlertList{} for _, alert := range alerts { + // strip labels user doesn't want to see in the UI + alert.Labels = transform.StripLables(config.Config.KeepLabels, config.Config.StripLabels, alert.Labels) // calculate final alert state based on the most important value found // in the list of states from all instances alertLFP := alert.LabelsFingerprint() diff --git a/internal/alertmanager/dedup_test.go b/internal/alertmanager/dedup_test.go index 1ed366400..bf8b424ab 100644 --- a/internal/alertmanager/dedup_test.go +++ b/internal/alertmanager/dedup_test.go @@ -35,9 +35,39 @@ func TestDedupAlerts(t *testing.T) { t.Error(err) } alertGroups := alertmanager.DedupAlerts() + if len(alertGroups) != 10 { t.Errorf("Expected %d alert groups, got %d", 10, len(alertGroups)) } + + totalAlerts := 0 + for _, ag := range alertGroups { + totalAlerts += len(ag.Alerts) + } + if totalAlerts != 24 { + t.Errorf("Expected %d total alerts, got %d", 24, totalAlerts) + } +} + +func TestDedupAlertsWithoutLabels(t *testing.T) { + config.Config.KeepLabels = []string{"xyz"} + if err := pullAlerts(); err != nil { + t.Error(err) + } + alertGroups := alertmanager.DedupAlerts() + config.Config.KeepLabels = []string{} + + if len(alertGroups) != 10 { + t.Errorf("Expected %d alert groups, got %d", 10, len(alertGroups)) + } + + totalAlerts := 0 + for _, ag := range alertGroups { + totalAlerts += len(ag.Alerts) + } + if totalAlerts != 24 { + t.Errorf("Expected %d total alerts, got %d", 24, totalAlerts) + } } func TestDedupAutocomplete(t *testing.T) { diff --git a/internal/alertmanager/models.go b/internal/alertmanager/models.go index ba6149e04..c1714d4a9 100644 --- a/internal/alertmanager/models.go +++ b/internal/alertmanager/models.go @@ -6,7 +6,6 @@ import ( "sync" "time" - "github.com/cloudflare/unsee/internal/config" "github.com/cloudflare/unsee/internal/mapper" "github.com/cloudflare/unsee/internal/models" "github.com/cloudflare/unsee/internal/transform" @@ -174,7 +173,6 @@ func (am *Alertmanager) pullAlerts(version string) error { } alert.Annotations, alert.Links = transform.DetectLinks(alert.Annotations) - alert.Labels = transform.StripLables(config.Config.StripLabels, alert.Labels) transform.ColorLabel(colors, "@receiver", alert.Receiver) for k, v := range alert.Labels { diff --git a/internal/config/config.go b/internal/config/config.go index 9ffed4ca0..9a503448b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -36,6 +36,7 @@ type configEnvs struct { SentryDSN string `envconfig:"SENTRY_DSN" help:"Sentry DSN for Go exceptions"` SentryPublicDSN string `envconfig:"SENTRY_PUBLIC_DSN" help:"Sentry DSN for javascript exceptions"` StripLabels spaceSeparatedList `envconfig:"STRIP_LABELS" help:"List of labels to ignore"` + KeepLabels spaceSeparatedList `envconfig:"KEEP_LABELS" help:"List of labels to keep, all other labels will be stripped"` WebPrefix string `envconfig:"WEB_PREFIX" default:"/" help:"URL prefix"` } diff --git a/internal/transform/strip.go b/internal/transform/strip.go index 91b9c71c4..7e25a115d 100644 --- a/internal/transform/strip.go +++ b/internal/transform/strip.go @@ -9,10 +9,16 @@ import ( // StripLables allows filtering out some labels from alerts // it takes the list of label keys to ignore and alert label map // it will return label map without labels found on the ignore list -func StripLables(ignoredLabels []string, sourceLabels map[string]string) map[string]string { +func StripLables(keptLabels, ignoredLabels []string, sourceLabels map[string]string) map[string]string { + // empty keep list means keep everything by default + keepAll := len(keptLabels) == 0 labels := map[string]string{} for label, value := range sourceLabels { - if !slices.StringInSlice(ignoredLabels, label) { + // is explicitly marked to be kept + inKeep := slices.StringInSlice(keptLabels, label) + // is explicitly marked to be stripped + inStrip := slices.StringInSlice(ignoredLabels, label) + if (keepAll || inKeep) && !inStrip { // strip leading and trailung space in label value // this is to normalize values in case space is added by Alertmanager rules labels[label] = strings.TrimSpace(value) diff --git a/internal/transform/strip_test.go b/internal/transform/strip_test.go index 2152588ea..a73f8d8f6 100644 --- a/internal/transform/strip_test.go +++ b/internal/transform/strip_test.go @@ -9,6 +9,7 @@ import ( type stripTest struct { strip []string + keep []string before map[string]string after map[string]string } @@ -16,6 +17,7 @@ type stripTest struct { var stripTests = []stripTest{ stripTest{ strip: []string{"env"}, + keep: []string{}, before: map[string]string{ "host": "localhost", "env": "production", @@ -28,6 +30,7 @@ var stripTests = []stripTest{ }, stripTest{ strip: []string{"server"}, + keep: []string{}, before: map[string]string{ "host": "localhost", "env": "production", @@ -41,6 +44,7 @@ var stripTests = []stripTest{ }, stripTest{ strip: []string{}, + keep: []string{}, before: map[string]string{ "host": "localhost", "env": "production", @@ -54,16 +58,50 @@ var stripTests = []stripTest{ }, stripTest{ strip: []string{"host"}, + keep: []string{}, before: map[string]string{ "host": "localhost", }, after: map[string]string{}, }, + stripTest{ + strip: []string{}, + keep: []string{"env"}, + before: map[string]string{ + "host": "localhost", + "env": "production", + "level": "info", + }, + after: map[string]string{ + "env": "production", + }, + }, + stripTest{ + strip: []string{"env"}, + keep: []string{"host"}, + before: map[string]string{ + "host": "localhost", + "env": "production", + "level": "info", + }, + after: map[string]string{ + "host": "localhost", + }, + }, + stripTest{ + strip: []string{}, + keep: []string{"env"}, + before: map[string]string{ + "host": "localhost", + "level": "info", + }, + after: map[string]string{}, + }, } func TestStripLables(t *testing.T) { for _, testCase := range stripTests { - labels := transform.StripLables(testCase.strip, testCase.before) + labels := transform.StripLables(testCase.keep, testCase.strip, testCase.before) if !reflect.DeepEqual(labels, testCase.after) { t.Errorf("StripLables failed, expected %v, got %v", testCase.after, labels) }