alertmanager.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. package notifiers
  2. import (
  3. "context"
  4. "regexp"
  5. "time"
  6. "github.com/grafana/grafana/pkg/bus"
  7. "github.com/grafana/grafana/pkg/components/simplejson"
  8. "github.com/grafana/grafana/pkg/infra/log"
  9. "github.com/grafana/grafana/pkg/models"
  10. "github.com/grafana/grafana/pkg/services/alerting"
  11. )
  12. func init() {
  13. alerting.RegisterNotifier(&alerting.NotifierPlugin{
  14. Type: "prometheus-alertmanager",
  15. Name: "Prometheus Alertmanager",
  16. Description: "Sends alert to Prometheus Alertmanager",
  17. Factory: NewAlertmanagerNotifier,
  18. OptionsTemplate: `
  19. <h3 class="page-heading">Alertmanager settings</h3>
  20. <div class="gf-form">
  21. <span class="gf-form-label width-10">Url</span>
  22. <input type="text" required class="gf-form-input max-width-26" ng-model="ctrl.model.settings.url" placeholder="http://localhost:9093"></input>
  23. </div>
  24. `,
  25. })
  26. }
  27. // NewAlertmanagerNotifier returns a new Alertmanager notifier
  28. func NewAlertmanagerNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
  29. url := model.Settings.Get("url").MustString()
  30. if url == "" {
  31. return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
  32. }
  33. return &AlertmanagerNotifier{
  34. NotifierBase: NewNotifierBase(model),
  35. URL: url,
  36. log: log.New("alerting.notifier.prometheus-alertmanager"),
  37. }, nil
  38. }
  39. // AlertmanagerNotifier sends alert notifications to the alert manager
  40. type AlertmanagerNotifier struct {
  41. NotifierBase
  42. URL string
  43. log log.Logger
  44. }
  45. // ShouldNotify returns true if the notifiers should be used depending on state
  46. func (am *AlertmanagerNotifier) ShouldNotify(ctx context.Context, evalContext *alerting.EvalContext, notificationState *models.AlertNotificationState) bool {
  47. am.log.Debug("Should notify", "ruleId", evalContext.Rule.ID, "state", evalContext.Rule.State, "previousState", evalContext.PrevAlertState)
  48. // Do not notify when we become OK for the first time.
  49. if (evalContext.PrevAlertState == models.AlertStatePending) && (evalContext.Rule.State == models.AlertStateOK) {
  50. return false
  51. }
  52. // Notify on Alerting -> OK to resolve before alertmanager timeout.
  53. if (evalContext.PrevAlertState == models.AlertStateAlerting) && (evalContext.Rule.State == models.AlertStateOK) {
  54. return true
  55. }
  56. return evalContext.Rule.State == models.AlertStateAlerting
  57. }
  58. func (am *AlertmanagerNotifier) createAlert(evalContext *alerting.EvalContext, match *alerting.EvalMatch, ruleURL string) *simplejson.Json {
  59. alertJSON := simplejson.New()
  60. alertJSON.Set("startsAt", evalContext.StartTime.UTC().Format(time.RFC3339))
  61. if evalContext.Rule.State == models.AlertStateOK {
  62. alertJSON.Set("endsAt", time.Now().UTC().Format(time.RFC3339))
  63. }
  64. alertJSON.Set("generatorURL", ruleURL)
  65. // Annotations (summary and description are very commonly used).
  66. alertJSON.SetPath([]string{"annotations", "summary"}, evalContext.Rule.Name)
  67. description := ""
  68. if evalContext.Rule.Message != "" {
  69. description += evalContext.Rule.Message
  70. }
  71. if evalContext.Error != nil {
  72. if description != "" {
  73. description += "\n"
  74. }
  75. description += "Error: " + evalContext.Error.Error()
  76. }
  77. if description != "" {
  78. alertJSON.SetPath([]string{"annotations", "description"}, description)
  79. }
  80. if evalContext.ImagePublicURL != "" {
  81. alertJSON.SetPath([]string{"annotations", "image"}, evalContext.ImagePublicURL)
  82. }
  83. // Labels (from metrics tags + AlertRuleTags + mandatory alertname).
  84. tags := make(map[string]string)
  85. if match != nil {
  86. if len(match.Tags) == 0 {
  87. tags["metric"] = match.Metric
  88. } else {
  89. for k, v := range match.Tags {
  90. tags[replaceIllegalCharsInLabelname(k)] = v
  91. }
  92. }
  93. }
  94. for _, tag := range evalContext.Rule.AlertRuleTags {
  95. tags[tag.Key] = tag.Value
  96. }
  97. tags["alertname"] = evalContext.Rule.Name
  98. alertJSON.Set("labels", tags)
  99. return alertJSON
  100. }
  101. // Notify sends alert notifications to the alert manager
  102. func (am *AlertmanagerNotifier) Notify(evalContext *alerting.EvalContext) error {
  103. am.log.Info("Sending Alertmanager alert", "ruleId", evalContext.Rule.ID, "notification", am.Name)
  104. ruleURL, err := evalContext.GetRuleURL()
  105. if err != nil {
  106. am.log.Error("Failed get rule link", "error", err)
  107. return err
  108. }
  109. // Send one alert per matching series.
  110. alerts := make([]interface{}, 0)
  111. for _, match := range evalContext.EvalMatches {
  112. alert := am.createAlert(evalContext, match, ruleURL)
  113. alerts = append(alerts, alert)
  114. }
  115. // This happens on ExecutionError or NoData
  116. if len(alerts) == 0 {
  117. alert := am.createAlert(evalContext, nil, ruleURL)
  118. alerts = append(alerts, alert)
  119. }
  120. bodyJSON := simplejson.NewFromAny(alerts)
  121. body, _ := bodyJSON.MarshalJSON()
  122. cmd := &models.SendWebhookSync{
  123. Url: am.URL + "/api/v1/alerts",
  124. HttpMethod: "POST",
  125. Body: string(body),
  126. }
  127. if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
  128. am.log.Error("Failed to send alertmanager", "error", err, "alertmanager", am.Name)
  129. return err
  130. }
  131. return nil
  132. }
  133. // regexp that matches all none valid label name characters
  134. // https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels
  135. var labelNamePattern = regexp.MustCompile(`[^a-zA-Z0-9_]`)
  136. func replaceIllegalCharsInLabelname(input string) string {
  137. return labelNamePattern.ReplaceAllString(input, "_")
  138. }