feat: added user selected filtering of channels in alerts (#1459)

This commit is contained in:
Amol Umbark 2022-08-04 15:31:21 +05:30 committed by GitHub
parent 425b732370
commit 80c96af5a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 85 additions and 74 deletions

View File

@ -40,6 +40,8 @@ type Alert struct {
StartsAt time.Time `json:"startsAt,omitempty"` StartsAt time.Time `json:"startsAt,omitempty"`
EndsAt time.Time `json:"endsAt,omitempty"` EndsAt time.Time `json:"endsAt,omitempty"`
GeneratorURL string `json:"generatorURL,omitempty"` GeneratorURL string `json:"generatorURL,omitempty"`
Receivers []string `json:"receivers,omitempty"`
} }
// Name returns the name of the alert. It is equivalent to the "alertname" label. // Name returns the name of the alert. It is equivalent to the "alertname" label.
@ -53,7 +55,7 @@ func (a *Alert) Hash() uint64 {
} }
func (a *Alert) String() string { func (a *Alert) String() string {
s := fmt.Sprintf("%s[%s]", a.Name(), fmt.Sprintf("%016x", a.Hash())[:7]) s := fmt.Sprintf("%s[%s][%s]", a.Name(), fmt.Sprintf("%016x", a.Hash())[:7], a.Receivers)
if a.Resolved() { if a.Resolved() {
return s + "[resolved]" return s + "[resolved]"
} }

View File

@ -72,6 +72,9 @@ type Alert struct {
GeneratorURL string GeneratorURL string
// list of preferred receivers, e.g. slack
Receivers []string
Value float64 Value float64
ActiveAt time.Time ActiveAt time.Time
FiredAt time.Time FiredAt time.Time
@ -80,7 +83,6 @@ type Alert struct {
ValidUntil time.Time ValidUntil time.Time
} }
// todo(amol): need to review this with ankit
func (a *Alert) needsSending(ts time.Time, resendDelay time.Duration) bool { func (a *Alert) needsSending(ts time.Time, resendDelay time.Duration) bool {
if a.State == StatePending { if a.State == StatePending {
return false return false

View File

@ -35,6 +35,8 @@ type PostableRule struct {
// Source captures the source url where rule has been created // Source captures the source url where rule has been created
Source string `json:"source,omitempty"` Source string `json:"source,omitempty"`
PreferredChannels []string `json:"preferredChannels,omitempty"`
// legacy // legacy
Expr string `yaml:"expr,omitempty" json:"expr,omitempty"` Expr string `yaml:"expr,omitempty" json:"expr,omitempty"`
OldYaml string `json:"yaml,omitempty"` OldYaml string `json:"yaml,omitempty"`

View File

@ -381,12 +381,7 @@ func (m *Manager) prepareTask(acquireLock bool, r *PostableRule, taskName string
// create a threshold rule // create a threshold rule
tr, err := NewThresholdRule( tr, err := NewThresholdRule(
ruleId, ruleId,
r.Alert, r,
r.RuleCondition,
time.Duration(r.EvalWindow),
r.Labels,
r.Annotations,
r.Source,
) )
if err != nil { if err != nil {
@ -406,14 +401,8 @@ func (m *Manager) prepareTask(acquireLock bool, r *PostableRule, taskName string
// create promql rule // create promql rule
pr, err := NewPromRule( pr, err := NewPromRule(
ruleId, ruleId,
r.Alert, r,
r.RuleCondition,
time.Duration(r.EvalWindow),
r.Labels,
r.Annotations,
// required as promql engine works with logger and not zap
log.With(m.logger, "alert", r.Alert), log.With(m.logger, "alert", r.Alert),
r.Source,
) )
if err != nil { if err != nil {
@ -521,6 +510,7 @@ func (m *Manager) prepareNotifyFunc() NotifyFunc {
Labels: alert.Labels, Labels: alert.Labels,
Annotations: alert.Annotations, Annotations: alert.Annotations,
GeneratorURL: generatorURL, GeneratorURL: generatorURL,
Receivers: alert.Receivers,
} }
if !alert.ResolvedAt.IsZero() { if !alert.ResolvedAt.IsZero() {
a.EndsAt = alert.ResolvedAt a.EndsAt = alert.ResolvedAt

View File

@ -29,6 +29,8 @@ type PromRule struct {
labels plabels.Labels labels plabels.Labels
annotations plabels.Labels annotations plabels.Labels
preferredChannels []string
mtx sync.Mutex mtx sync.Mutex
evaluationDuration time.Duration evaluationDuration time.Duration
evaluationTimestamp time.Time evaluationTimestamp time.Time
@ -45,38 +47,37 @@ type PromRule struct {
func NewPromRule( func NewPromRule(
id string, id string,
name string, postableRule *PostableRule,
ruleCondition *RuleCondition,
evalWindow time.Duration,
labels, annotations map[string]string,
logger log.Logger, logger log.Logger,
source string,
) (*PromRule, error) { ) (*PromRule, error) {
if int64(evalWindow) == 0 { if postableRule.RuleCondition == nil {
evalWindow = 5 * time.Minute
}
if ruleCondition == nil {
return nil, fmt.Errorf("no rule condition") return nil, fmt.Errorf("no rule condition")
} else if !ruleCondition.IsValid() { } else if !postableRule.RuleCondition.IsValid() {
return nil, fmt.Errorf("invalid rule condition") return nil, fmt.Errorf("invalid rule condition")
} }
zap.S().Info("msg:", "creating new alerting rule", "\t name:", name, "\t condition:", ruleCondition.String()) p := PromRule{
id: id,
name: postableRule.Alert,
source: postableRule.Source,
ruleCondition: postableRule.RuleCondition,
evalWindow: time.Duration(postableRule.EvalWindow),
labels: plabels.FromMap(postableRule.Labels),
annotations: plabels.FromMap(postableRule.Annotations),
preferredChannels: postableRule.PreferredChannels,
health: HealthUnknown,
active: map[uint64]*Alert{},
logger: logger,
}
return &PromRule{ if int64(p.evalWindow) == 0 {
id: id, p.evalWindow = 5 * time.Minute
name: name, }
source: source,
ruleCondition: ruleCondition, zap.S().Info("msg:", "creating new alerting rule", "\t name:", p.name, "\t condition:", p.ruleCondition.String())
evalWindow: evalWindow,
labels: plabels.FromMap(labels), return &p, nil
annotations: plabels.FromMap(annotations),
health: HealthUnknown,
active: map[uint64]*Alert{},
logger: logger,
}, nil
} }
func (r *PromRule) Name() string { func (r *PromRule) Name() string {
@ -99,6 +100,10 @@ func (r *PromRule) GeneratorURL() string {
return prepareRuleGeneratorURL(r.ID(), r.source) return prepareRuleGeneratorURL(r.ID(), r.source)
} }
func (r *PromRule) PreferredChannels() []string {
return r.preferredChannels
}
func (r *PromRule) SetLastError(err error) { func (r *PromRule) SetLastError(err error) {
r.mtx.Lock() r.mtx.Lock()
defer r.mtx.Unlock() defer r.mtx.Unlock()
@ -382,6 +387,7 @@ func (r *PromRule) Eval(ctx context.Context, ts time.Time, queriers *Queriers) (
State: StatePending, State: StatePending,
Value: smpl.V, Value: smpl.V,
GeneratorURL: r.GeneratorURL(), GeneratorURL: r.GeneratorURL(),
Receivers: r.preferredChannels,
} }
} }
@ -392,6 +398,7 @@ func (r *PromRule) Eval(ctx context.Context, ts time.Time, queriers *Queriers) (
if alert, ok := r.active[h]; ok && alert.State != StateInactive { if alert, ok := r.active[h]; ok && alert.State != StateInactive {
alert.Value = a.Value alert.Value = a.Value
alert.Annotations = a.Annotations alert.Annotations = a.Annotations
alert.Receivers = r.preferredChannels
continue continue
} }
@ -429,11 +436,12 @@ func (r *PromRule) Eval(ctx context.Context, ts time.Time, queriers *Queriers) (
func (r *PromRule) String() string { func (r *PromRule) String() string {
ar := PostableRule{ ar := PostableRule{
Alert: r.name, Alert: r.name,
RuleCondition: r.ruleCondition, RuleCondition: r.ruleCondition,
EvalWindow: Duration(r.evalWindow), EvalWindow: Duration(r.evalWindow),
Labels: r.labels.Map(), Labels: r.labels.Map(),
Annotations: r.annotations.Map(), Annotations: r.annotations.Map(),
PreferredChannels: r.preferredChannels,
} }
byt, err := yaml.Marshal(ar) byt, err := yaml.Marshal(ar)

View File

@ -19,6 +19,8 @@ type Rule interface {
State() AlertState State() AlertState
ActiveAlerts() []*Alert ActiveAlerts() []*Alert
PreferredChannels() []string
Eval(context.Context, time.Time, *Queriers) (interface{}, error) Eval(context.Context, time.Time, *Queriers) (interface{}, error)
String() string String() string
// Query() string // Query() string

View File

@ -32,6 +32,7 @@ type ThresholdRule struct {
labels labels.Labels labels labels.Labels
annotations labels.Labels annotations labels.Labels
preferredChannels []string
mtx sync.Mutex mtx sync.Mutex
evaluationDuration time.Duration evaluationDuration time.Duration
evaluationTimestamp time.Time evaluationTimestamp time.Time
@ -46,39 +47,35 @@ type ThresholdRule struct {
func NewThresholdRule( func NewThresholdRule(
id string, id string,
name string, p *PostableRule,
ruleCondition *RuleCondition,
evalWindow time.Duration,
l, a map[string]string,
source string,
) (*ThresholdRule, error) { ) (*ThresholdRule, error) {
if int64(evalWindow) == 0 { if p.RuleCondition == nil {
evalWindow = 5 * time.Minute
}
if ruleCondition == nil {
return nil, fmt.Errorf("no rule condition") return nil, fmt.Errorf("no rule condition")
} else if !ruleCondition.IsValid() { } else if !p.RuleCondition.IsValid() {
return nil, fmt.Errorf("invalid rule condition") return nil, fmt.Errorf("invalid rule condition")
} }
thresholdRule := &ThresholdRule{ t := ThresholdRule{
id: id, id: id,
name: name, name: p.Alert,
source: source, source: p.Source,
ruleCondition: ruleCondition, ruleCondition: p.RuleCondition,
evalWindow: evalWindow, evalWindow: time.Duration(p.EvalWindow),
labels: labels.FromMap(l), labels: labels.FromMap(p.Labels),
annotations: labels.FromMap(a), annotations: labels.FromMap(p.Annotations),
preferredChannels: p.PreferredChannels,
health: HealthUnknown, health: HealthUnknown,
active: map[uint64]*Alert{}, active: map[uint64]*Alert{},
} }
zap.S().Info("msg:", "creating new alerting rule", "\t name:", name, "\t condition:", ruleCondition.String(), "\t generatorURL:", thresholdRule.GeneratorURL()) if int64(t.evalWindow) == 0 {
t.evalWindow = 5 * time.Minute
}
return thresholdRule, nil zap.S().Info("msg:", "creating new alerting rule", "\t name:", t.name, "\t condition:", t.ruleCondition.String(), "\t generatorURL:", t.GeneratorURL())
return &t, nil
} }
func (r *ThresholdRule) Name() string { func (r *ThresholdRule) Name() string {
@ -97,6 +94,10 @@ func (r *ThresholdRule) GeneratorURL() string {
return prepareRuleGeneratorURL(r.ID(), r.source) return prepareRuleGeneratorURL(r.ID(), r.source)
} }
func (r *ThresholdRule) PreferredChannels() []string {
return r.preferredChannels
}
func (r *ThresholdRule) target() *float64 { func (r *ThresholdRule) target() *float64 {
if r.ruleCondition == nil { if r.ruleCondition == nil {
return nil return nil
@ -479,6 +480,7 @@ func (r *ThresholdRule) runChQuery(ctx context.Context, db clickhouse.Conn, quer
} }
} }
} }
zap.S().Debugf("ruleid:", r.ID(), "\t resultmap(potential alerts):", len(resultMap))
for _, sample := range resultMap { for _, sample := range resultMap {
// check alert rule condition before dumping results // check alert rule condition before dumping results
@ -486,7 +488,7 @@ func (r *ThresholdRule) runChQuery(ctx context.Context, db clickhouse.Conn, quer
result = append(result, sample) result = append(result, sample)
} }
} }
zap.S().Debugf("ruleid:", r.ID(), "\t result (found alerts):", len(result))
return result, nil return result, nil
} }
@ -615,6 +617,7 @@ func (r *ThresholdRule) Eval(ctx context.Context, ts time.Time, queriers *Querie
State: StatePending, State: StatePending,
Value: smpl.V, Value: smpl.V,
GeneratorURL: r.GeneratorURL(), GeneratorURL: r.GeneratorURL(),
Receivers: r.preferredChannels,
} }
} }
@ -628,6 +631,7 @@ func (r *ThresholdRule) Eval(ctx context.Context, ts time.Time, queriers *Querie
alert.Value = a.Value alert.Value = a.Value
alert.Annotations = a.Annotations alert.Annotations = a.Annotations
alert.Receivers = r.preferredChannels
continue continue
} }
@ -665,11 +669,12 @@ func (r *ThresholdRule) Eval(ctx context.Context, ts time.Time, queriers *Querie
func (r *ThresholdRule) String() string { func (r *ThresholdRule) String() string {
ar := PostableRule{ ar := PostableRule{
Alert: r.name, Alert: r.name,
RuleCondition: r.ruleCondition, RuleCondition: r.ruleCondition,
EvalWindow: Duration(r.evalWindow), EvalWindow: Duration(r.evalWindow),
Labels: r.labels.Map(), Labels: r.labels.Map(),
Annotations: r.annotations.Map(), Annotations: r.annotations.Map(),
PreferredChannels: r.preferredChannels,
} }
byt, err := yaml.Marshal(ar) byt, err := yaml.Marshal(ar)