mirror of
https://github.com/prometheus/alertmanager
synced 2025-01-11 16:29:30 +00:00
Merge pull request #156 from prometheus/beorn7/review-dev
PR with changes after code review
This commit is contained in:
commit
a4ffa9a64b
10
README.md
10
README.md
@ -54,8 +54,8 @@ This is an example configuration that should cover most relevant aspects of the
|
||||
```yaml
|
||||
global:
|
||||
# The smarthost and SMTP sender used for mail notifications.
|
||||
smarthost: 'localhost:25'
|
||||
smtp_sender: 'alertmanager@example.org'
|
||||
smtp_smarthost: 'localhost:25'
|
||||
smtp_from: 'alertmanager@example.org'
|
||||
|
||||
# The directory from which notification templates are read.
|
||||
templates:
|
||||
@ -155,17 +155,17 @@ inhibit_rules:
|
||||
receivers:
|
||||
- name: 'team-X-mails'
|
||||
email_configs:
|
||||
- email: 'team-X+alerts@example.org'
|
||||
- to: 'team-X+alerts@example.org'
|
||||
|
||||
- name: 'team-X-pager'
|
||||
email_configs:
|
||||
- email: 'team-X+alerts-critical@example.org'
|
||||
- to: 'team-X+alerts-critical@example.org'
|
||||
pagerduty_configs:
|
||||
- service_key: <team-X-key>
|
||||
|
||||
- name: 'team-Y-mails'
|
||||
email_configs:
|
||||
- email: 'team-Y+alerts@example.org'
|
||||
- to: 'team-Y+alerts@example.org'
|
||||
|
||||
- name: 'team-Y-pager'
|
||||
pagerduty_configs:
|
||||
|
@ -14,6 +14,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
@ -31,6 +32,13 @@ func Load(s string) (*Config, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Check if we have a root route. We cannot check for it in the
|
||||
// UnmarshalYAML method because it won't be called if the input is empty
|
||||
// (e.g. the config file is empty or only contains whitespace).
|
||||
if cfg.Route == nil {
|
||||
return nil, errors.New("no route provided in config")
|
||||
}
|
||||
|
||||
cfg.original = s
|
||||
return cfg, nil
|
||||
}
|
||||
@ -134,7 +142,7 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
}
|
||||
if ec.From == "" {
|
||||
if c.Global.SMTPFrom == "" {
|
||||
return fmt.Errorf("no global SMTP sender set")
|
||||
return fmt.Errorf("no global SMTP from set")
|
||||
}
|
||||
ec.From = c.Global.SMTPFrom
|
||||
}
|
||||
@ -294,12 +302,12 @@ type Receiver struct {
|
||||
// A unique identifier for this receiver.
|
||||
Name string `yaml:"name"`
|
||||
|
||||
PagerdutyConfigs []*PagerdutyConfig `yaml:"pagerduty_configs"`
|
||||
EmailConfigs []*EmailConfig `yaml:"email_configs"`
|
||||
PushoverConfigs []*PushoverConfig `yaml:"pushover_configs"`
|
||||
HipchatConfigs []*HipchatConfig `yaml:"hipchat_configs"`
|
||||
SlackConfigs []*SlackConfig `yaml:"slack_configs"`
|
||||
FlowdockConfigs []*FlowdockConfig `yaml:"flowdock_configs"`
|
||||
HipchatConfigs []*HipchatConfig `yaml:"hipchat_configs"`
|
||||
PagerdutyConfigs []*PagerdutyConfig `yaml:"pagerduty_configs"`
|
||||
PushoverConfigs []*PushoverConfig `yaml:"pushover_configs"`
|
||||
SlackConfigs []*SlackConfig `yaml:"slack_configs"`
|
||||
WebhookConfigs []*WebhookConfig `yaml:"webhook_configs"`
|
||||
|
||||
// Catches all undefined fields and must be empty after parsing.
|
||||
|
@ -19,12 +19,25 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultEmailConfig defines default values for Email configurations.
|
||||
DefaultEmailConfig = EmailConfig{
|
||||
HTML: `{{template "email.default.html" .}}`,
|
||||
}
|
||||
// DefaultEmailSubject is a template used if no email subject has been provided.
|
||||
DefaultEmailSubject = `{{template "email.default.subject" .}}`
|
||||
|
||||
// DefaultHipchatConfig defines default values for Hipchat configurations.
|
||||
DefaultHipchatConfig = HipchatConfig{
|
||||
Color: `{{ if eq .Status "firing" }}purple{{ else }}green{{ end }}`,
|
||||
MessageFormat: HipchatFormatHTML,
|
||||
}
|
||||
|
||||
// DefaultPagerdutyConfig defines default values for PagerDuty configurations.
|
||||
DefaultPagerdutyConfig = PagerdutyConfig{
|
||||
Description: `{{template "pagerduty.default.description" .}}`,
|
||||
// TODO: Add a details field with all the alerts.
|
||||
}
|
||||
|
||||
// DefaultSlackConfig defines default values for Slack configurations.
|
||||
DefaultSlackConfig = SlackConfig{
|
||||
Color: `{{ if eq .Status "firing" }}warning{{ else }}good{{ end }}`,
|
||||
@ -34,42 +47,36 @@ var (
|
||||
Text: `{{template "slack.default.text" . }}`,
|
||||
Fallback: `{{template "slack.default.fallback" . }}`,
|
||||
}
|
||||
|
||||
// DefaultEmailConfig defines default values for Email configurations.
|
||||
DefaultEmailConfig = EmailConfig{
|
||||
HTML: `{{template "email.default.html" .}}`,
|
||||
}
|
||||
DefaultEmailSubject = `{{template "email.default.subject" .}}`
|
||||
|
||||
// DefaultPagerdutyConfig defines default values for PagerDuty configurations.
|
||||
DefaultPagerdutyConfig = PagerdutyConfig{
|
||||
Description: `{{template "pagerduty.default.description" .}}`,
|
||||
// TODO: Add a details field with all the alerts.
|
||||
}
|
||||
)
|
||||
|
||||
// PagerdutyConfig configures notifications via PagerDuty.
|
||||
type PagerdutyConfig struct {
|
||||
ServiceKey string `yaml:"service_key"`
|
||||
URL string `yaml:"url"`
|
||||
Description string `yaml:"description"`
|
||||
Details map[string]string `yaml:"details"`
|
||||
// FlowdockConfig configures notifications via Flowdock.
|
||||
type FlowdockConfig struct {
|
||||
// Flowdock flow API token.
|
||||
APIToken string `yaml:"api_token"`
|
||||
|
||||
// Flowdock from_address.
|
||||
FromAddress string `yaml:"from_address"`
|
||||
|
||||
// Flowdock flow tags.
|
||||
Tags []string `yaml:"tags"`
|
||||
|
||||
// Catches all undefined fields and must be empty after parsing.
|
||||
XXX map[string]interface{} `yaml:",inline"`
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (c *PagerdutyConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
*c = DefaultPagerdutyConfig
|
||||
type plain PagerdutyConfig
|
||||
func (c *FlowdockConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
type plain FlowdockConfig
|
||||
if err := unmarshal((*plain)(c)); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.ServiceKey == "" {
|
||||
return fmt.Errorf("missing service key in PagerDuty config")
|
||||
if c.APIToken == "" {
|
||||
return fmt.Errorf("missing API token in Flowdock config")
|
||||
}
|
||||
return checkOverflow(c.XXX, "pagerduty config")
|
||||
if c.FromAddress == "" {
|
||||
return fmt.Errorf("missing from address in Flowdock config")
|
||||
}
|
||||
return checkOverflow(c.XXX, "flowdock config")
|
||||
}
|
||||
|
||||
// EmailConfig configures notifications via mail.
|
||||
@ -97,15 +104,15 @@ func (c *EmailConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
}
|
||||
|
||||
// Header names are case-insensitive, check for collisions.
|
||||
normalisedHeaders := map[string]string{}
|
||||
normalizedHeaders := map[string]string{}
|
||||
for h, v := range c.Headers {
|
||||
normalised := strings.ToTitle(h)
|
||||
if _, ok := normalisedHeaders[normalised]; ok {
|
||||
return fmt.Errorf("duplicate header %q in email config", normalised)
|
||||
normalized := strings.ToTitle(h)
|
||||
if _, ok := normalizedHeaders[normalized]; ok {
|
||||
return fmt.Errorf("duplicate header %q in email config", normalized)
|
||||
}
|
||||
normalisedHeaders[normalised] = v
|
||||
normalizedHeaders[normalized] = v
|
||||
}
|
||||
c.Headers = normalisedHeaders
|
||||
c.Headers = normalizedHeaders
|
||||
if _, ok := c.Headers["Subject"]; !ok {
|
||||
c.Headers["Subject"] = DefaultEmailSubject
|
||||
}
|
||||
@ -119,36 +126,10 @@ func (c *EmailConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
return checkOverflow(c.XXX, "email config")
|
||||
}
|
||||
|
||||
// PushoverConfig configures notifications via PushOver.
|
||||
type PushoverConfig struct {
|
||||
// Pushover token.
|
||||
Token string `yaml:"token"`
|
||||
|
||||
// Pushover user_key.
|
||||
UserKey string `yaml:"user_key"`
|
||||
|
||||
// Catches all undefined fields and must be empty after parsing.
|
||||
XXX map[string]interface{} `yaml:",inline"`
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (c *PushoverConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
type plain PushoverConfig
|
||||
if err := unmarshal((*plain)(c)); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.Token == "" {
|
||||
return fmt.Errorf("missing token in Pushover config")
|
||||
}
|
||||
if c.UserKey == "" {
|
||||
return fmt.Errorf("missing user key in Pushover config")
|
||||
}
|
||||
return checkOverflow(c.XXX, "pushover config")
|
||||
}
|
||||
|
||||
// HipchatFormat defines text formats for Hipchat.
|
||||
type HipchatFormat string
|
||||
|
||||
// Possible values of HipchatFormat.
|
||||
const (
|
||||
HipchatFormatHTML HipchatFormat = "html"
|
||||
HipchatFormatText HipchatFormat = "text"
|
||||
@ -195,6 +176,57 @@ func (c *HipchatConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
return checkOverflow(c.XXX, "hipchat config")
|
||||
}
|
||||
|
||||
// PagerdutyConfig configures notifications via PagerDuty.
|
||||
type PagerdutyConfig struct {
|
||||
ServiceKey string `yaml:"service_key"`
|
||||
URL string `yaml:"url"`
|
||||
Description string `yaml:"description"`
|
||||
Details map[string]string `yaml:"details"`
|
||||
|
||||
// Catches all undefined fields and must be empty after parsing.
|
||||
XXX map[string]interface{} `yaml:",inline"`
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (c *PagerdutyConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
*c = DefaultPagerdutyConfig
|
||||
type plain PagerdutyConfig
|
||||
if err := unmarshal((*plain)(c)); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.ServiceKey == "" {
|
||||
return fmt.Errorf("missing service key in PagerDuty config")
|
||||
}
|
||||
return checkOverflow(c.XXX, "pagerduty config")
|
||||
}
|
||||
|
||||
// PushoverConfig configures notifications via PushOver.
|
||||
type PushoverConfig struct {
|
||||
// Pushover token.
|
||||
Token string `yaml:"token"`
|
||||
|
||||
// Pushover user_key.
|
||||
UserKey string `yaml:"user_key"`
|
||||
|
||||
// Catches all undefined fields and must be empty after parsing.
|
||||
XXX map[string]interface{} `yaml:",inline"`
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (c *PushoverConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
type plain PushoverConfig
|
||||
if err := unmarshal((*plain)(c)); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.Token == "" {
|
||||
return fmt.Errorf("missing token in Pushover config")
|
||||
}
|
||||
if c.UserKey == "" {
|
||||
return fmt.Errorf("missing user key in Pushover config")
|
||||
}
|
||||
return checkOverflow(c.XXX, "pushover config")
|
||||
}
|
||||
|
||||
// SlackConfig configures notifications via Slack.
|
||||
type SlackConfig struct {
|
||||
URL string `yaml:"url"`
|
||||
@ -228,36 +260,6 @@ func (c *SlackConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
return checkOverflow(c.XXX, "slack config")
|
||||
}
|
||||
|
||||
// FlowdockConfig configures notifications via Flowdock.
|
||||
type FlowdockConfig struct {
|
||||
// Flowdock flow API token.
|
||||
APIToken string `yaml:"api_token"`
|
||||
|
||||
// Flowdock from_address.
|
||||
FromAddress string `yaml:"from_address"`
|
||||
|
||||
// Flowdock flow tags.
|
||||
Tags []string `yaml:"tags"`
|
||||
|
||||
// Catches all undefined fields and must be empty after parsing.
|
||||
XXX map[string]interface{} `yaml:",inline"`
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (c *FlowdockConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
type plain FlowdockConfig
|
||||
if err := unmarshal((*plain)(c)); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.APIToken == "" {
|
||||
return fmt.Errorf("missing API token in Flowdock config")
|
||||
}
|
||||
if c.FromAddress == "" {
|
||||
return fmt.Errorf("missing from address in Flowdock config")
|
||||
}
|
||||
return checkOverflow(c.XXX, "flowdock config")
|
||||
}
|
||||
|
||||
// WebhookConfig configures notifications via a generic webhook.
|
||||
type WebhookConfig struct {
|
||||
// URL to send POST request to.
|
||||
|
17
dispatch.go
17
dispatch.go
@ -17,6 +17,7 @@ import (
|
||||
|
||||
// ResolveTimeout is the time after which an alert is declared resolved
|
||||
// if it has not been updated.
|
||||
// TODO(fabxc): Make it configurable.
|
||||
const ResolveTimeout = 5 * time.Minute
|
||||
|
||||
// Dispatcher sorts incoming alerts into aggregation groups and
|
||||
@ -152,7 +153,15 @@ func (d *Dispatcher) run(it provider.AlertIterator) {
|
||||
|
||||
for {
|
||||
select {
|
||||
case alert := <-it.Next():
|
||||
case alert, ok := <-it.Next():
|
||||
if !ok {
|
||||
// Iterator exhausted for some reason.
|
||||
if err := it.Err(); err != nil {
|
||||
log.Errorf("Error on alert update: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
d.log.With("alert", alert).Debug("Received alert")
|
||||
|
||||
// Log errors but keep trying.
|
||||
@ -278,7 +287,7 @@ func newAggrGroup(ctx context.Context, labels model.LabelSet, opts *RouteOpts) *
|
||||
}
|
||||
|
||||
func (ag *aggrGroup) String() string {
|
||||
return fmt.Sprintf("%v", ag.fingerprint())
|
||||
return fmt.Sprint(ag.fingerprint())
|
||||
}
|
||||
|
||||
func (ag *aggrGroup) alertSlice() []*types.Alert {
|
||||
@ -352,8 +361,8 @@ func (ag *aggrGroup) fingerprint() model.Fingerprint {
|
||||
return ag.labels.Fingerprint()
|
||||
}
|
||||
|
||||
// insert the alert into the aggregation group. If the aggregation group
|
||||
// is empty afterwards, true is returned.
|
||||
// insert inserts the alert into the aggregation group. If the aggregation group
|
||||
// is empty afterwards, it returns true.
|
||||
func (ag *aggrGroup) insert(alert *types.Alert) {
|
||||
ag.mtx.Lock()
|
||||
defer ag.mtx.Unlock()
|
||||
|
@ -1,7 +1,7 @@
|
||||
global:
|
||||
# The smarthost and SMTP sender used for mail notifications.
|
||||
smarthost: 'localhost:25'
|
||||
smtp_sender: 'alertmanager@example.org'
|
||||
smtp_smarthost: 'localhost:25'
|
||||
smtp_from: 'alertmanager@example.org'
|
||||
|
||||
# The directory from which notification templates are read.
|
||||
templates:
|
||||
@ -96,17 +96,17 @@ inhibit_rules:
|
||||
receivers:
|
||||
- name: 'team-X-mails'
|
||||
email_configs:
|
||||
- email: 'team-X+alerts@example.org'
|
||||
- to: 'team-X+alerts@example.org'
|
||||
|
||||
- name: 'team-X-pager'
|
||||
email_configs:
|
||||
- email: 'team-X+alerts-critical@example.org'
|
||||
- to: 'team-X+alerts-critical@example.org'
|
||||
pagerduty_configs:
|
||||
- service_key: <team-X-key>
|
||||
|
||||
- name: 'team-Y-mails'
|
||||
email_configs:
|
||||
- email: 'team-Y+alerts@example.org'
|
||||
- to: 'team-Y+alerts@example.org'
|
||||
|
||||
- name: 'team-Y-pager'
|
||||
pagerduty_configs:
|
||||
|
17
inhibit.go
17
inhibit.go
@ -69,6 +69,9 @@ func (ih *Inhibitor) Mutes(lset model.LabelSet) bool {
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := alerts.Err(); err != nil {
|
||||
log.Errorf("Error after iterating alerts: %s", err)
|
||||
}
|
||||
|
||||
ih.marker.SetInhibited(lset.Fingerprint(), false)
|
||||
|
||||
@ -103,24 +106,14 @@ func NewInhibitRule(cr *config.InhibitRule) *InhibitRule {
|
||||
sourcem = append(sourcem, types.NewMatcher(model.LabelName(ln), lv))
|
||||
}
|
||||
for ln, lv := range cr.SourceMatchRE {
|
||||
m, err := types.NewRegexMatcher(model.LabelName(ln), lv.String())
|
||||
if err != nil {
|
||||
// Must have been sanitized during config validation.
|
||||
panic(err)
|
||||
}
|
||||
sourcem = append(sourcem, m)
|
||||
sourcem = append(sourcem, types.NewRegexMatcher(model.LabelName(ln), lv.Regexp))
|
||||
}
|
||||
|
||||
for ln, lv := range cr.TargetMatch {
|
||||
targetm = append(targetm, types.NewMatcher(model.LabelName(ln), lv))
|
||||
}
|
||||
for ln, lv := range cr.TargetMatchRE {
|
||||
m, err := types.NewRegexMatcher(model.LabelName(ln), lv.String())
|
||||
if err != nil {
|
||||
// Must have been sanitized during config validation.
|
||||
panic(err)
|
||||
}
|
||||
targetm = append(targetm, m)
|
||||
targetm = append(targetm, types.NewRegexMatcher(model.LabelName(ln), lv.Regexp))
|
||||
}
|
||||
|
||||
equal := map[model.LabelName]struct{}{}
|
||||
|
14
main.go
14
main.go
@ -24,7 +24,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
template_text "text/template"
|
||||
tmpltext "text/template"
|
||||
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/route"
|
||||
@ -40,10 +40,10 @@ import (
|
||||
var (
|
||||
showVersion = flag.Bool("version", false, "Print version information.")
|
||||
|
||||
configFile = flag.String("config.file", "alertmanager.yml", "The configuration file")
|
||||
dataDir = flag.String("storage.path", "data/", "The data directory")
|
||||
configFile = flag.String("config.file", "alertmanager.yml", "Alertmanager configuration file name.")
|
||||
dataDir = flag.String("storage.path", "data/", "Base path for data storage.")
|
||||
|
||||
externalURL = flag.String("web.external-url", "", "The URL under which Prometheus is externally reachable (for example, if Prometheus is served via a reverse proxy). Used for generating relative and absolute links back to Prometheus itself. If the URL has a path portion, it will be used to prefix all HTTP endpoints served by Prometheus. If omitted, relevant URL components will be derived automatically.")
|
||||
externalURL = flag.String("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.")
|
||||
listenAddress = flag.String("web.listen-address", ":9093", "Address to listen on for the web interface and API.")
|
||||
)
|
||||
|
||||
@ -71,7 +71,7 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
notifies, err := provider.NewSQLNotifyInfo(db)
|
||||
notifies, err := provider.NewSQLNotifies(db)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@ -107,7 +107,7 @@ func main() {
|
||||
}
|
||||
router[name] = fo
|
||||
}
|
||||
var n notify.Notifier = router
|
||||
n := notify.Notifier(router)
|
||||
|
||||
n = notify.Log(n, log.With("step", "route"))
|
||||
n = notify.Mute(silences, n)
|
||||
@ -185,7 +185,7 @@ alertmanager, version {{.version}} (branch: {{.branch}}, revision: {{.revision}}
|
||||
`
|
||||
|
||||
func printVersion() {
|
||||
t := template_text.Must(template_text.New("version").Parse(versionInfoTmpl))
|
||||
t := tmpltext.Must(tmpltext.New("version").Parse(versionInfoTmpl))
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := t.ExecuteTemplate(&buf, "version", version.Map); err != nil {
|
||||
|
@ -145,7 +145,7 @@ func (ns Fanout) Notify(ctx context.Context, alerts ...*types.Alert) error {
|
||||
|
||||
go func(n Notifier) {
|
||||
if err := n.Notify(foCtx, alerts...); err != nil {
|
||||
me = append(me, err)
|
||||
me.Add(err)
|
||||
log.Errorf("Error on notify: %s", err)
|
||||
}
|
||||
wg.Done()
|
||||
@ -154,8 +154,8 @@ func (ns Fanout) Notify(ctx context.Context, alerts ...*types.Alert) error {
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if len(me) > 0 {
|
||||
return me
|
||||
if me.Len() > 0 {
|
||||
return &me
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -340,6 +340,7 @@ func Mute(m types.Muter, n Notifier) *MutingNotifier {
|
||||
return &MutingNotifier{Muter: m, notifier: n}
|
||||
}
|
||||
|
||||
// Notify implements Notifier.
|
||||
func (n *MutingNotifier) Notify(ctx context.Context, alerts ...*types.Alert) error {
|
||||
var filtered []*types.Alert
|
||||
for _, a := range alerts {
|
||||
|
@ -14,7 +14,6 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
@ -22,16 +21,14 @@ import (
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = fmt.Errorf("item not found")
|
||||
)
|
||||
|
||||
// MemData contains the data backing MemAlerts and MemNotifies.
|
||||
type MemData struct {
|
||||
mtx sync.RWMutex
|
||||
alerts map[model.Fingerprint]*types.Alert
|
||||
notifies map[string]map[model.Fingerprint]*types.NotifyInfo
|
||||
}
|
||||
|
||||
// NewMemData contains an empty but initialized MemData instance.
|
||||
func NewMemData() *MemData {
|
||||
return &MemData{
|
||||
alerts: map[model.Fingerprint]*types.Alert{},
|
||||
@ -39,19 +36,6 @@ func NewMemData() *MemData {
|
||||
}
|
||||
}
|
||||
|
||||
type memAlertIterator struct {
|
||||
ch <-chan *types.Alert
|
||||
done chan struct{}
|
||||
err error
|
||||
}
|
||||
|
||||
func (ai memAlertIterator) Next() <-chan *types.Alert {
|
||||
return ai.ch
|
||||
}
|
||||
|
||||
func (ai memAlertIterator) Err() error { return ai.err }
|
||||
func (ai memAlertIterator) Close() { close(ai.done) }
|
||||
|
||||
// MemAlerts implements an Alerts provider based on in-memory data.
|
||||
type MemAlerts struct {
|
||||
data *MemData
|
||||
@ -61,6 +45,7 @@ type MemAlerts struct {
|
||||
next int
|
||||
}
|
||||
|
||||
// NewMemAlerts returns a new MemAlerts based on the provided data.
|
||||
func NewMemAlerts(data *MemData) *MemAlerts {
|
||||
return &MemAlerts{
|
||||
data: data,
|
||||
@ -68,6 +53,7 @@ func NewMemAlerts(data *MemData) *MemAlerts {
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe implements the Alerts interface.
|
||||
func (a *MemAlerts) Subscribe() AlertIterator {
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
@ -104,12 +90,13 @@ func (a *MemAlerts) Subscribe() AlertIterator {
|
||||
<-done
|
||||
}()
|
||||
|
||||
return memAlertIterator{
|
||||
return alertIterator{
|
||||
ch: ch,
|
||||
done: done,
|
||||
}
|
||||
}
|
||||
|
||||
// GetPending implements the Alerts interface.
|
||||
func (a *MemAlerts) GetPending() AlertIterator {
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
@ -134,7 +121,7 @@ func (a *MemAlerts) GetPending() AlertIterator {
|
||||
}
|
||||
}()
|
||||
|
||||
return memAlertIterator{
|
||||
return alertIterator{
|
||||
ch: ch,
|
||||
done: done,
|
||||
}
|
||||
@ -163,6 +150,7 @@ func (a *MemAlerts) getPending() []*types.Alert {
|
||||
return alerts
|
||||
}
|
||||
|
||||
// Put implements the Alerts interface.
|
||||
func (a *MemAlerts) Put(alerts ...*types.Alert) error {
|
||||
a.mtx.Lock()
|
||||
defer a.mtx.Unlock()
|
||||
@ -187,6 +175,7 @@ func (a *MemAlerts) Put(alerts ...*types.Alert) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get implements the Alerts interface.
|
||||
func (a *MemAlerts) Get(fp model.Fingerprint) (*types.Alert, error) {
|
||||
a.data.mtx.RLock()
|
||||
defer a.data.mtx.RUnlock()
|
||||
@ -197,14 +186,17 @@ func (a *MemAlerts) Get(fp model.Fingerprint) (*types.Alert, error) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
// MemNotifies implements a Notifies provider based on in-memory data.
|
||||
type MemNotifies struct {
|
||||
data *MemData
|
||||
}
|
||||
|
||||
// NewMemNotifies returns a new MemNotifies based on the provided data.
|
||||
func NewMemNotifies(data *MemData) *MemNotifies {
|
||||
return &MemNotifies{data: data}
|
||||
}
|
||||
|
||||
// Set implements the Notifies interface.
|
||||
func (n *MemNotifies) Set(ns ...*types.NotifyInfo) error {
|
||||
n.data.mtx.Lock()
|
||||
defer n.data.mtx.Unlock()
|
||||
@ -223,6 +215,7 @@ func (n *MemNotifies) Set(ns ...*types.NotifyInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get implements the Notifies interface.
|
||||
func (n *MemNotifies) Get(dest string, fps ...model.Fingerprint) ([]*types.NotifyInfo, error) {
|
||||
n.data.mtx.RLock()
|
||||
defer n.data.mtx.RUnlock()
|
||||
@ -241,17 +234,20 @@ func (n *MemNotifies) Get(dest string, fps ...model.Fingerprint) ([]*types.Notif
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// MemSilences implements a Silences provider based on in-memory data.
|
||||
type MemSilences struct {
|
||||
mtx sync.RWMutex
|
||||
silences map[uint64]*model.Silence
|
||||
}
|
||||
|
||||
// NewMemSilences returns a new MemSilences.
|
||||
func NewMemSilences() *MemSilences {
|
||||
return &MemSilences{
|
||||
silences: map[uint64]*model.Silence{},
|
||||
}
|
||||
}
|
||||
|
||||
// Mutes implements the Muter interface.
|
||||
func (s *MemSilences) Mutes(lset model.LabelSet) bool {
|
||||
s.mtx.RLock()
|
||||
defer s.mtx.RUnlock()
|
||||
@ -264,6 +260,7 @@ func (s *MemSilences) Mutes(lset model.LabelSet) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// All implements the Silences interface.
|
||||
func (s *MemSilences) All() ([]*types.Silence, error) {
|
||||
s.mtx.RLock()
|
||||
defer s.mtx.RUnlock()
|
||||
@ -275,6 +272,7 @@ func (s *MemSilences) All() ([]*types.Silence, error) {
|
||||
return sils, nil
|
||||
}
|
||||
|
||||
// Set impelements the Silences interface.
|
||||
func (s *MemSilences) Set(sil *types.Silence) (uint64, error) {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
@ -291,6 +289,7 @@ func (s *MemSilences) Set(sil *types.Silence) (uint64, error) {
|
||||
return sil.ID, nil
|
||||
}
|
||||
|
||||
// Del implements the Silences interface.
|
||||
func (s *MemSilences) Del(id uint64) error {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
@ -299,6 +298,7 @@ func (s *MemSilences) Del(id uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get implements the Silences interface.
|
||||
func (s *MemSilences) Get(id uint64) (*types.Silence, error) {
|
||||
s.mtx.RLock()
|
||||
defer s.mtx.RUnlock()
|
||||
|
@ -14,22 +14,56 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotFound is returned if a provider cannot find a requested item.
|
||||
ErrNotFound = fmt.Errorf("item not found")
|
||||
)
|
||||
|
||||
// Iterator provides the functions common to all iterators. To be useful, a
|
||||
// specific iterator interface (e.g. AlertIterator) has to be implemented that
|
||||
// provides a Next method.
|
||||
type Iterator interface {
|
||||
// Err returns the current error. It is not safe to call it concurrently
|
||||
// with other iterator methods or while reading from a channel returned
|
||||
// by the iterator.
|
||||
Err() error
|
||||
// Close must be called to release resources once the iterator is not
|
||||
// used anymore.
|
||||
Close()
|
||||
}
|
||||
|
||||
// AlertIterator is an Iterator for Alerts.
|
||||
type AlertIterator interface {
|
||||
Iterator
|
||||
// Next returns a channel that will be closed once the iterator is
|
||||
// exhausted. It is not necessary to exhaust the iterator but Close must
|
||||
// be called in any case to release resources used by the iterator (even
|
||||
// if the iterator is exhausted).
|
||||
Next() <-chan *types.Alert
|
||||
}
|
||||
|
||||
// Alerts gives access to a set of alerts.
|
||||
// alertIterator implements AlertIterator. So far, this one fits all providers.
|
||||
type alertIterator struct {
|
||||
ch <-chan *types.Alert
|
||||
done chan struct{}
|
||||
err error
|
||||
}
|
||||
|
||||
func (ai alertIterator) Next() <-chan *types.Alert {
|
||||
return ai.ch
|
||||
}
|
||||
|
||||
func (ai alertIterator) Err() error { return ai.err }
|
||||
func (ai alertIterator) Close() { close(ai.done) }
|
||||
|
||||
// Alerts gives access to a set of alerts. All methods are goroutine-safe.
|
||||
type Alerts interface {
|
||||
// Subscribe returns an iterator over active alerts that have not been
|
||||
// resolved and successfully notified about.
|
||||
@ -44,7 +78,7 @@ type Alerts interface {
|
||||
Put(...*types.Alert) error
|
||||
}
|
||||
|
||||
// Silences gives access to silences.
|
||||
// Silences gives access to silences. All methods are goroutine-safe.
|
||||
type Silences interface {
|
||||
// The Silences provider must implement the Muter interface
|
||||
// for all its silences. The data provider may have access to an
|
||||
@ -62,7 +96,7 @@ type Silences interface {
|
||||
}
|
||||
|
||||
// Notifies provides information about pending and successful
|
||||
// notifications.
|
||||
// notifications. All methods are goroutine-safe.
|
||||
type Notifies interface {
|
||||
Get(dest string, fps ...model.Fingerprint) ([]*types.NotifyInfo, error)
|
||||
// Set several notifies at once. All or none must succeed.
|
||||
|
@ -30,11 +30,13 @@ func init() {
|
||||
ql.RegisterDriver()
|
||||
}
|
||||
|
||||
type SQLNotifyInfo struct {
|
||||
// SQLNotifies implements a Notifies provider based on a SQL DB.
|
||||
type SQLNotifies struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewSQLNotifyInfo(db *sql.DB) (*SQLNotifyInfo, error) {
|
||||
// NewSQLNotifies returns a new SQLNotifies based on the provided SQL DB.
|
||||
func NewSQLNotifies(db *sql.DB) (*SQLNotifies, error) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -69,7 +71,7 @@ func NewSQLNotifyInfo(db *sql.DB) (*SQLNotifyInfo, error) {
|
||||
|
||||
tx.Commit()
|
||||
|
||||
return &SQLNotifyInfo{db: db}, nil
|
||||
return &SQLNotifies{db: db}, nil
|
||||
}
|
||||
|
||||
const createNotifyInfoTable = `
|
||||
@ -85,7 +87,8 @@ CREATE INDEX IF NOT EXISTS notify_done
|
||||
ON notify_info (resolved);
|
||||
`
|
||||
|
||||
func (n *SQLNotifyInfo) Get(dest string, fps ...model.Fingerprint) ([]*types.NotifyInfo, error) {
|
||||
// Get implements the Notifies interface.
|
||||
func (n *SQLNotifies) Get(dest string, fps ...model.Fingerprint) ([]*types.NotifyInfo, error) {
|
||||
var result []*types.NotifyInfo
|
||||
|
||||
for _, fp := range fps {
|
||||
@ -120,7 +123,8 @@ func (n *SQLNotifyInfo) Get(dest string, fps ...model.Fingerprint) ([]*types.Not
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (n *SQLNotifyInfo) Set(ns ...*types.NotifyInfo) error {
|
||||
// Set implements the Notifies interface.
|
||||
func (n *SQLNotifies) Set(ns ...*types.NotifyInfo) error {
|
||||
tx, err := n.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -167,6 +171,7 @@ func (n *SQLNotifyInfo) Set(ns ...*types.NotifyInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SQLAlerts implements an Alerts provider based on a SQL DB.
|
||||
type SQLAlerts struct {
|
||||
db *sql.DB
|
||||
|
||||
@ -175,6 +180,7 @@ type SQLAlerts struct {
|
||||
next int
|
||||
}
|
||||
|
||||
// NewSQLAlerts returns a new SQLAlerts based on the provided SQL DB.
|
||||
func NewSQLAlerts(db *sql.DB) (*SQLAlerts, error) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
@ -206,6 +212,7 @@ CREATE INDEX IF NOT EXISTS alerts_start ON alerts (starts_at);
|
||||
CREATE INDEX IF NOT EXISTS alerts_end ON alerts (ends_at);
|
||||
`
|
||||
|
||||
// Subscribe implements the Alerts interface.
|
||||
func (a *SQLAlerts) Subscribe() AlertIterator {
|
||||
var (
|
||||
ch = make(chan *types.Alert, 200)
|
||||
@ -239,13 +246,14 @@ func (a *SQLAlerts) Subscribe() AlertIterator {
|
||||
<-done
|
||||
}()
|
||||
|
||||
return memAlertIterator{
|
||||
return alertIterator{
|
||||
ch: ch,
|
||||
done: done,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// GetPending implements the Alerts interface.
|
||||
func (a *SQLAlerts) GetPending() AlertIterator {
|
||||
var (
|
||||
ch = make(chan *types.Alert, 200)
|
||||
@ -266,7 +274,7 @@ func (a *SQLAlerts) GetPending() AlertIterator {
|
||||
}
|
||||
}()
|
||||
|
||||
return memAlertIterator{
|
||||
return alertIterator{
|
||||
ch: ch,
|
||||
done: done,
|
||||
err: err,
|
||||
@ -323,10 +331,12 @@ func (a *SQLAlerts) getPending() ([]*types.Alert, error) {
|
||||
return alerts, nil
|
||||
}
|
||||
|
||||
// Get implements the Alerts interface.
|
||||
func (a *SQLAlerts) Get(model.Fingerprint) (*types.Alert, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Put implements the Alerts interface.
|
||||
func (a *SQLAlerts) Put(alerts ...*types.Alert) error {
|
||||
tx, err := a.db.Begin()
|
||||
if err != nil {
|
||||
@ -473,11 +483,13 @@ func (a *SQLAlerts) Put(alerts ...*types.Alert) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SQLSilences implements a Silences provider based on a SQL DB.
|
||||
type SQLSilences struct {
|
||||
db *sql.DB
|
||||
marker types.Marker
|
||||
}
|
||||
|
||||
// NewSQLSilences returns a new SQLSilences based on the provided SQL DB.
|
||||
func NewSQLSilences(db *sql.DB, mk types.Marker) (*SQLSilences, error) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
@ -506,6 +518,7 @@ CREATE INDEX IF NOT EXISTS silences_end ON silences (ends_at);
|
||||
CREATE INDEX IF NOT EXISTS silences_id ON silences (id());
|
||||
`
|
||||
|
||||
// Mutes implements the Muter interface.
|
||||
func (s *SQLSilences) Mutes(lset model.LabelSet) bool {
|
||||
sils, err := s.All()
|
||||
if err != nil {
|
||||
@ -525,6 +538,7 @@ func (s *SQLSilences) Mutes(lset model.LabelSet) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// All implements the Silences interface.
|
||||
func (s *SQLSilences) All() ([]*types.Silence, error) {
|
||||
rows, err := s.db.Query(`
|
||||
SELECT id(), matchers, starts_at, ends_at, created_at, created_by, comment
|
||||
@ -569,6 +583,7 @@ func (s *SQLSilences) All() ([]*types.Silence, error) {
|
||||
return silences, nil
|
||||
}
|
||||
|
||||
// Set impelements the Silences interface.
|
||||
func (s *SQLSilences) Set(sil *types.Silence) (uint64, error) {
|
||||
mb, err := json.Marshal(sil.Silence.Matchers)
|
||||
if err != nil {
|
||||
@ -607,6 +622,7 @@ func (s *SQLSilences) Set(sil *types.Silence) (uint64, error) {
|
||||
return uint64(sid), nil
|
||||
}
|
||||
|
||||
// Del implements the Silences interface.
|
||||
func (s *SQLSilences) Del(sid uint64) error {
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
@ -622,6 +638,7 @@ func (s *SQLSilences) Del(sid uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get implements the Silences interface.
|
||||
func (s *SQLSilences) Get(sid uint64) (*types.Silence, error) {
|
||||
row := s.db.QueryRow(`
|
||||
SELECT id(), matchers, starts_at, ends_at, created_at, created_by, comment
|
||||
|
7
route.go
7
route.go
@ -91,12 +91,7 @@ func NewRoute(cr *config.Route, parent *Route) *Route {
|
||||
matchers = append(matchers, types.NewMatcher(model.LabelName(ln), lv))
|
||||
}
|
||||
for ln, lv := range cr.MatchRE {
|
||||
m, err := types.NewRegexMatcher(model.LabelName(ln), lv.String())
|
||||
if err != nil {
|
||||
// Must have been sanitized during config validation.
|
||||
panic(err)
|
||||
}
|
||||
matchers = append(matchers, m)
|
||||
matchers = append(matchers, types.NewRegexMatcher(model.LabelName(ln), lv.Regexp))
|
||||
}
|
||||
|
||||
route := &Route{
|
||||
|
@ -50,7 +50,7 @@ routes:
|
||||
continue: true
|
||||
|
||||
- match_re:
|
||||
env: "^produ.*$"
|
||||
env: "produ.*"
|
||||
|
||||
receiver: 'notify-productionB'
|
||||
group_wait: 30s
|
||||
@ -60,7 +60,7 @@ routes:
|
||||
|
||||
|
||||
- match_re:
|
||||
owner: '^team-(B|C)$'
|
||||
owner: 'team-(B|C)'
|
||||
|
||||
group_by: ['foo', 'bar']
|
||||
group_wait: 2m
|
||||
|
@ -16,19 +16,22 @@ package template
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
html_tmpl "html/template"
|
||||
text_tmpl "text/template"
|
||||
tmplhtml "html/template"
|
||||
tmpltext "text/template"
|
||||
)
|
||||
|
||||
// Template bundles a text and a html template instance.
|
||||
type Template struct {
|
||||
text *text_tmpl.Template
|
||||
html *html_tmpl.Template
|
||||
text *tmpltext.Template
|
||||
html *tmplhtml.Template
|
||||
}
|
||||
|
||||
// FromGlobs calls ParseGlob on all path globs provided and returns the
|
||||
// resulting Template.
|
||||
func FromGlobs(paths ...string) (*Template, error) {
|
||||
t := &Template{
|
||||
text: text_tmpl.New("").Option("missingkey=zero"),
|
||||
html: html_tmpl.New("").Option("missingkey=zero"),
|
||||
text: tmpltext.New("").Option("missingkey=zero"),
|
||||
html: tmplhtml.New("").Option("missingkey=zero"),
|
||||
}
|
||||
var err error
|
||||
|
||||
@ -44,6 +47,7 @@ func FromGlobs(paths ...string) (*Template, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// ExecuteTextString needs a meaningful doc comment (TODO(fabxc)).
|
||||
func (t *Template) ExecuteTextString(text string, data interface{}) (string, error) {
|
||||
tmpl, err := t.text.Clone()
|
||||
if err != nil {
|
||||
@ -58,6 +62,7 @@ func (t *Template) ExecuteTextString(text string, data interface{}) (string, err
|
||||
return buf.String(), err
|
||||
}
|
||||
|
||||
// ExecuteHTMLString needs a meaningful doc comment (TODO(fabxc)).
|
||||
func (t *Template) ExecuteHTMLString(html string, data interface{}) (string, error) {
|
||||
tmpl, err := t.html.Clone()
|
||||
if err != nil {
|
||||
|
@ -263,7 +263,8 @@ func (am *Alertmanager) Start() {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
// kill the underlying Alertmanager process and remove intermediate data.
|
||||
// Terminate kills the underlying Alertmanager process and remove intermediate
|
||||
// data.
|
||||
func (am *Alertmanager) Terminate() {
|
||||
syscall.Kill(am.cmd.Process.Pid, syscall.SIGTERM)
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ func (c *Collector) latest() float64 {
|
||||
return latest
|
||||
}
|
||||
|
||||
// want declares that the Collector expects to receive the given alerts
|
||||
// Want declares that the Collector expects to receive the given alerts
|
||||
// within the given time boundaries.
|
||||
func (c *Collector) Want(iv Interval, alerts ...*TestAlert) {
|
||||
var nas model.Alerts
|
||||
|
24
test/mock.go
24
test/mock.go
@ -84,28 +84,28 @@ func (s *TestSilence) MatchRE(v ...string) *TestSilence {
|
||||
|
||||
// nativeSilence converts the declared test silence into a regular
|
||||
// silence with resolved times.
|
||||
func (sil *TestSilence) nativeSilence(opts *AcceptanceOpts) *model.Silence {
|
||||
func (s *TestSilence) nativeSilence(opts *AcceptanceOpts) *model.Silence {
|
||||
nsil := &model.Silence{}
|
||||
|
||||
for i := 0; i < len(sil.match); i += 2 {
|
||||
for i := 0; i < len(s.match); i += 2 {
|
||||
nsil.Matchers = append(nsil.Matchers, &model.Matcher{
|
||||
Name: model.LabelName(sil.match[i]),
|
||||
Value: sil.match[i+1],
|
||||
Name: model.LabelName(s.match[i]),
|
||||
Value: s.match[i+1],
|
||||
})
|
||||
}
|
||||
for i := 0; i < len(sil.matchRE); i += 2 {
|
||||
for i := 0; i < len(s.matchRE); i += 2 {
|
||||
nsil.Matchers = append(nsil.Matchers, &model.Matcher{
|
||||
Name: model.LabelName(sil.matchRE[i]),
|
||||
Value: sil.matchRE[i+1],
|
||||
Name: model.LabelName(s.matchRE[i]),
|
||||
Value: s.matchRE[i+1],
|
||||
IsRegex: true,
|
||||
})
|
||||
}
|
||||
|
||||
if sil.startsAt > 0 {
|
||||
nsil.StartsAt = opts.expandTime(sil.startsAt)
|
||||
if s.startsAt > 0 {
|
||||
nsil.StartsAt = opts.expandTime(s.startsAt)
|
||||
}
|
||||
if sil.endsAt > 0 {
|
||||
nsil.EndsAt = opts.expandTime(sil.endsAt)
|
||||
if s.endsAt > 0 {
|
||||
nsil.EndsAt = opts.expandTime(s.endsAt)
|
||||
}
|
||||
return nsil
|
||||
}
|
||||
@ -117,7 +117,7 @@ type TestAlert struct {
|
||||
startsAt, endsAt float64
|
||||
}
|
||||
|
||||
// alert creates a new alert declaration with the given key/value pairs
|
||||
// Alert creates a new alert declaration with the given key/value pairs
|
||||
// as identifying labels.
|
||||
func Alert(keyval ...interface{}) *TestAlert {
|
||||
if len(keyval)%2 == 1 {
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
@ -32,11 +33,12 @@ type Matcher struct {
|
||||
|
||||
func (m *Matcher) String() string {
|
||||
if m.isRegex {
|
||||
return fmt.Sprintf("<RegexMatcher %s:%q>", m.Name, m.regex)
|
||||
return fmt.Sprintf("<RegexMatcher %s:%q>", m.Name, m.Value)
|
||||
}
|
||||
return fmt.Sprintf("<Matcher %s:%q>", m.Name, m.Value)
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (m *Matcher) MarshalJSON() ([]byte, error) {
|
||||
v := struct {
|
||||
Name model.LabelName `json:"name"`
|
||||
@ -80,20 +82,21 @@ func NewMatcher(name model.LabelName, value string) *Matcher {
|
||||
|
||||
// NewRegexMatcher returns a new matcher that treats value as a regular
|
||||
// expression which is used for matching.
|
||||
func NewRegexMatcher(name model.LabelName, value string) (*Matcher, error) {
|
||||
re, err := regexp.Compile(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func NewRegexMatcher(name model.LabelName, re *regexp.Regexp) *Matcher {
|
||||
value := strings.TrimSuffix(strings.TrimPrefix(re.String(), "^(?:"), ")$")
|
||||
if len(re.String())-len(value) != 6 {
|
||||
// Any non-anchored regexp is a bug.
|
||||
panic(fmt.Errorf("regexp %q not properly anchored", re))
|
||||
}
|
||||
m := &Matcher{
|
||||
return &Matcher{
|
||||
Name: name,
|
||||
Value: value,
|
||||
isRegex: true,
|
||||
regex: re,
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Matchers provides the Match and Fingerprint methods for a slice of Matchers.
|
||||
type Matchers []*Matcher
|
||||
|
||||
// Match checks whether all matchers are fulfilled against the given label set.
|
||||
@ -106,6 +109,7 @@ func (ms Matchers) Match(lset model.LabelSet) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Fingerprint returns a quasi-unique fingerprint for the matchers.
|
||||
func (ms Matchers) Fingerprint() model.Fingerprint {
|
||||
lset := make(model.LabelSet, 3*len(ms))
|
||||
|
||||
|
@ -16,6 +16,7 @@ package types
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@ -23,6 +24,8 @@ import (
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// Marker helps to mark alerts as silenced and/or inhibited.
|
||||
// All methods are goroutine-safe.
|
||||
type Marker interface {
|
||||
SetInhibited(alert model.Fingerprint, b bool)
|
||||
SetSilenced(alert model.Fingerprint, sil ...uint64)
|
||||
@ -31,6 +34,7 @@ type Marker interface {
|
||||
Inhibited(alert model.Fingerprint) bool
|
||||
}
|
||||
|
||||
// NewMarker returns an instance of a Marker implementation.
|
||||
func NewMarker() Marker {
|
||||
return &memMarker{
|
||||
inhibited: map[model.Fingerprint]struct{}{},
|
||||
@ -83,11 +87,44 @@ func (m *memMarker) SetSilenced(alert model.Fingerprint, sil ...uint64) {
|
||||
}
|
||||
}
|
||||
|
||||
type MultiError []error
|
||||
// MultiError contains multiple errors and implements the error interface. Its
|
||||
// zero value is ready to use. All its methods are goroutine safe.
|
||||
type MultiError struct {
|
||||
mtx sync.Mutex
|
||||
errors []error
|
||||
}
|
||||
|
||||
func (e MultiError) Error() string {
|
||||
var es []string
|
||||
for _, err := range e {
|
||||
// Add adds an error to the MultiError.
|
||||
func (e *MultiError) Add(err error) {
|
||||
e.mtx.Lock()
|
||||
defer e.mtx.Unlock()
|
||||
|
||||
e.errors = append(e.errors, err)
|
||||
}
|
||||
|
||||
// Len returns the number of errors added to the MultiError.
|
||||
func (e *MultiError) Len() int {
|
||||
e.mtx.Lock()
|
||||
defer e.mtx.Unlock()
|
||||
|
||||
return len(e.errors)
|
||||
}
|
||||
|
||||
// Errors returns the errors added to the MuliError. The returned slice is a
|
||||
// copy of the internal slice of errors.
|
||||
func (e *MultiError) Errors() []error {
|
||||
e.mtx.Lock()
|
||||
defer e.mtx.Unlock()
|
||||
|
||||
return append(make([]error, 0, len(e.errors)), e.errors...)
|
||||
}
|
||||
|
||||
func (e *MultiError) Error() string {
|
||||
e.mtx.Lock()
|
||||
defer e.mtx.Unlock()
|
||||
|
||||
es := make([]string, 0, len(e.errors))
|
||||
for _, err := range e.errors {
|
||||
es = append(es, err.Error())
|
||||
}
|
||||
return strings.Join(es, "; ")
|
||||
@ -105,6 +142,7 @@ type Alert struct {
|
||||
Timeout bool `json:"-"`
|
||||
}
|
||||
|
||||
// AlertSlice is a sortable slice of Alerts.
|
||||
type AlertSlice []*Alert
|
||||
|
||||
func (as AlertSlice) Less(i, j int) bool { return as[i].UpdatedAt.Before(as[j].UpdatedAt) }
|
||||
@ -114,7 +152,7 @@ func (as AlertSlice) Len() int { return len(as) }
|
||||
// Alerts turns a sequence of internal alerts into a list of
|
||||
// exposable model.Alert structures.
|
||||
func Alerts(alerts ...*Alert) model.Alerts {
|
||||
var res model.Alerts
|
||||
res := make(model.Alerts, 0, len(alerts))
|
||||
for _, a := range alerts {
|
||||
v := a.Alert
|
||||
// If the end timestamp was set as the expected value in case
|
||||
@ -127,9 +165,9 @@ func Alerts(alerts ...*Alert) model.Alerts {
|
||||
return res
|
||||
}
|
||||
|
||||
// Merges the timespan of two alerts based and overwrites annotations
|
||||
// based on the authoritative timestamp.
|
||||
// A new alert is returned, the labels are assumed to be equal.
|
||||
// Merge merges the timespan of two alerts based and overwrites annotations
|
||||
// based on the authoritative timestamp. A new alert is returned, the labels
|
||||
// are assumed to be equal.
|
||||
func (a *Alert) Merge(o *Alert) *Alert {
|
||||
// Let o always be the younger alert.
|
||||
if o.UpdatedAt.Before(a.UpdatedAt) {
|
||||
@ -152,13 +190,15 @@ func (a *Alert) Merge(o *Alert) *Alert {
|
||||
return &res
|
||||
}
|
||||
|
||||
// A Silencer determines whether a given label set is muted.
|
||||
// A Muter determines whether a given label set is muted.
|
||||
type Muter interface {
|
||||
Mutes(model.LabelSet) bool
|
||||
}
|
||||
|
||||
// A MuteFunc is a function that implements the Muter interface.
|
||||
type MuteFunc func(model.LabelSet) bool
|
||||
|
||||
// Mutes implements the Muter interface.
|
||||
func (f MuteFunc) Mutes(lset model.LabelSet) bool { return f(lset) }
|
||||
|
||||
// A Silence determines whether a given label set is muted
|
||||
@ -186,16 +226,13 @@ func NewSilence(s *model.Silence) *Silence {
|
||||
sil.Matchers = append(sil.Matchers, NewMatcher(m.Name, m.Value))
|
||||
continue
|
||||
}
|
||||
rem, err := NewRegexMatcher(m.Name, m.Value)
|
||||
if err != nil {
|
||||
// Must have been sanitized beforehand.
|
||||
panic(err)
|
||||
}
|
||||
rem := NewRegexMatcher(m.Name, regexp.MustCompile("^(?:"+m.Value+")$"))
|
||||
sil.Matchers = append(sil.Matchers, rem)
|
||||
}
|
||||
return sil
|
||||
}
|
||||
|
||||
// Mutes implements the Muter interface.
|
||||
func (sil *Silence) Mutes(lset model.LabelSet) bool {
|
||||
t := sil.timeFunc()
|
||||
|
||||
@ -221,6 +258,7 @@ func (n *NotifyInfo) String() string {
|
||||
return fmt.Sprintf("<Notify:%q@%s to=%v res=%v>", n.Alert, n.Timestamp, n.Receiver, n.Resolved)
|
||||
}
|
||||
|
||||
// Fingerprint returns a quasi-unique fingerprint for the NotifyInfo.
|
||||
func (n *NotifyInfo) Fingerprint() model.Fingerprint {
|
||||
h := fnv.New64a()
|
||||
h.Write([]byte(n.Receiver))
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user