diff --git a/cmd/alertmanager/main.go b/cmd/alertmanager/main.go index 40f9c83d5f..55de352910 100644 --- a/cmd/alertmanager/main.go +++ b/cmd/alertmanager/main.go @@ -150,6 +150,7 @@ func run() int { maxSilences = kingpin.Flag("silences.max-silences", "Maximum number of silences, including expired silences. If negative or zero, no limit is set.").Default("0").Int() maxSilenceSizeBytes = kingpin.Flag("silences.max-silence-size-bytes", "Maximum silence size in bytes. If negative or zero, no limit is set.").Default("0").Int() alertGCInterval = kingpin.Flag("alerts.gc-interval", "Interval between alert GC.").Default("30m").Duration() + silenceLogging = kingpin.Flag("silences.logging", "Enable logging silences. If it is enabled, the status change of silence will be logged").Bool() webConfig = webflag.AddFlags(kingpin.CommandLine, ":9093") externalURL = kingpin.Flag("web.external-url", "The URL under which Alertmanager is externally reachable (for example, if Alertmanager is served via a reverse proxy). Used for generating relative and absolute links back to Alertmanager itself. If the URL has a path portion, it will be used to prefix all HTTP endpoints served by Alertmanager. If omitted, relevant URL components will be derived automatically.").String() @@ -299,6 +300,7 @@ func run() int { }, Logger: logger.With("component", "silences"), Metrics: prometheus.DefaultRegisterer, + Logging: *silenceLogging, } silences, err := silence.New(silenceOpts) diff --git a/silence/silence.go b/silence/silence.go index 7e25dc3b7b..2b1b920a28 100644 --- a/silence/silence.go +++ b/silence/silence.go @@ -26,6 +26,7 @@ import ( "reflect" "regexp" "sort" + "strings" "sync" "time" @@ -194,6 +195,7 @@ type Silences struct { metrics *metrics retention time.Duration limits Limits + logging bool mtx sync.RWMutex st state @@ -331,6 +333,8 @@ type Options struct { Retention time.Duration Limits Limits + Logging bool + // A logger used by background processing. Logger *slog.Logger Metrics prometheus.Registerer @@ -355,6 +359,7 @@ func New(o Options) (*Silences, error) { logger: promslog.NewNopLogger(), retention: o.Retention, limits: o.Limits, + logging: o.Logging, broadcast: func([]byte) {}, st: state{}, } @@ -615,6 +620,9 @@ func (s *Silences) Set(sil *pb.Silence) error { msil := s.toMeshSilence(sil) if err := s.checkSizeLimits(msil); err != nil { return err + if s.logging { + s.logginSilences("update silence", sil) + } } return s.setSilence(msil, now) } @@ -652,6 +660,9 @@ func (s *Silences) Set(sil *pb.Silence) error { } } + if s.logging { + s.logginSilences("create silence", sil) + } return s.setSilence(msil, now) } @@ -711,6 +722,9 @@ func (s *Silences) expire(id string) error { sil.EndsAt = now } sil.UpdatedAt = now + if s.logging { + s.logginSilences("expire silence", sil) + } return s.setSilence(s.toMeshSilence(sil), now) } @@ -1053,3 +1067,30 @@ func openReplace(filename string) (*replaceFile, error) { } return rf, nil } + +// logging silence status changes. +func (s *Silences) logginSilences(msg string, sil *pb.Silence) { + var listMatchers []string + var matcher_type_operator = map[string]string{ + "EQUAL": "=", + "REGEXP": "=~", + "NOT_EQUAL": "!=", + "NOT_REGEXP": "!~", + } + for _, matcher := range sil.Matchers { + ms := []string{matcher.Name, matcher_type_operator[matcher.Type.String()], matcher.Pattern} + m := strings.Join(ms, ``) + listMatchers = append(listMatchers, m) + } + strMatchers := strings.Join(listMatchers, `,`) + + s.logger.Info( + "msg", msg, + "Id", sil.Id, + "CreatedBy", sil.CreatedBy, + "Comment", sil.Comment, + "StartsAt", sil.StartsAt, + "EndsAt", sil.EndsAt, + "Matchers", strMatchers, + ) +}