opsgenie.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. package notifiers
  2. import (
  3. "fmt"
  4. "strconv"
  5. "github.com/grafana/grafana/pkg/bus"
  6. "github.com/grafana/grafana/pkg/components/simplejson"
  7. "github.com/grafana/grafana/pkg/infra/log"
  8. "github.com/grafana/grafana/pkg/models"
  9. "github.com/grafana/grafana/pkg/services/alerting"
  10. )
  11. func init() {
  12. alerting.RegisterNotifier(&alerting.NotifierPlugin{
  13. Type: "opsgenie",
  14. Name: "OpsGenie",
  15. Description: "Sends notifications to OpsGenie",
  16. Factory: NewOpsGenieNotifier,
  17. OptionsTemplate: `
  18. <h3 class="page-heading">OpsGenie settings</h3>
  19. <div class="gf-form">
  20. <span class="gf-form-label width-14">API Key</span>
  21. <input type="text" required class="gf-form-input max-width-22" ng-model="ctrl.model.settings.apiKey" placeholder="OpsGenie API Key"></input>
  22. </div>
  23. <div class="gf-form">
  24. <span class="gf-form-label width-14">Alert API Url</span>
  25. <input type="text" required class="gf-form-input max-width-22" ng-model="ctrl.model.settings.apiUrl" placeholder="https://api.opsgenie.com/v2/alerts"></input>
  26. </div>
  27. <div class="gf-form">
  28. <gf-form-switch
  29. class="gf-form"
  30. label="Auto close incidents"
  31. label-class="width-14"
  32. checked="ctrl.model.settings.autoClose"
  33. tooltip="Automatically close alerts in OpsGenie once the alert goes back to ok.">
  34. </gf-form-switch>
  35. </div>
  36. `,
  37. })
  38. }
  39. var (
  40. opsgenieAlertURL = "https://api.opsgenie.com/v2/alerts"
  41. )
  42. // NewOpsGenieNotifier is the constructor for OpsGenie.
  43. func NewOpsGenieNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
  44. autoClose := model.Settings.Get("autoClose").MustBool(true)
  45. apiKey := model.Settings.Get("apiKey").MustString()
  46. apiURL := model.Settings.Get("apiUrl").MustString()
  47. if apiKey == "" {
  48. return nil, alerting.ValidationError{Reason: "Could not find api key property in settings"}
  49. }
  50. if apiURL == "" {
  51. apiURL = opsgenieAlertURL
  52. }
  53. return &OpsGenieNotifier{
  54. NotifierBase: NewNotifierBase(model),
  55. APIKey: apiKey,
  56. APIUrl: apiURL,
  57. AutoClose: autoClose,
  58. log: log.New("alerting.notifier.opsgenie"),
  59. }, nil
  60. }
  61. // OpsGenieNotifier is responsible for sending
  62. // alert notifications to OpsGenie
  63. type OpsGenieNotifier struct {
  64. NotifierBase
  65. APIKey string
  66. APIUrl string
  67. AutoClose bool
  68. log log.Logger
  69. }
  70. // Notify sends an alert notification to OpsGenie.
  71. func (on *OpsGenieNotifier) Notify(evalContext *alerting.EvalContext) error {
  72. var err error
  73. switch evalContext.Rule.State {
  74. case models.AlertStateOK:
  75. if on.AutoClose {
  76. err = on.closeAlert(evalContext)
  77. }
  78. case models.AlertStateAlerting:
  79. err = on.createAlert(evalContext)
  80. }
  81. return err
  82. }
  83. func (on *OpsGenieNotifier) createAlert(evalContext *alerting.EvalContext) error {
  84. on.log.Info("Creating OpsGenie alert", "ruleId", evalContext.Rule.ID, "notification", on.Name)
  85. ruleURL, err := evalContext.GetRuleURL()
  86. if err != nil {
  87. on.log.Error("Failed get rule link", "error", err)
  88. return err
  89. }
  90. customData := triggMetrString
  91. for _, evt := range evalContext.EvalMatches {
  92. customData = customData + fmt.Sprintf("%s: %v\n", evt.Metric, evt.Value)
  93. }
  94. bodyJSON := simplejson.New()
  95. bodyJSON.Set("message", evalContext.Rule.Name)
  96. bodyJSON.Set("source", "Grafana")
  97. bodyJSON.Set("alias", "alertId-"+strconv.FormatInt(evalContext.Rule.ID, 10))
  98. bodyJSON.Set("description", fmt.Sprintf("%s - %s\n%s\n%s", evalContext.Rule.Name, ruleURL, evalContext.Rule.Message, customData))
  99. details := simplejson.New()
  100. details.Set("url", ruleURL)
  101. if evalContext.ImagePublicURL != "" {
  102. details.Set("image", evalContext.ImagePublicURL)
  103. }
  104. bodyJSON.Set("details", details)
  105. body, _ := bodyJSON.MarshalJSON()
  106. cmd := &models.SendWebhookSync{
  107. Url: on.APIUrl,
  108. Body: string(body),
  109. HttpMethod: "POST",
  110. HttpHeader: map[string]string{
  111. "Content-Type": "application/json",
  112. "Authorization": fmt.Sprintf("GenieKey %s", on.APIKey),
  113. },
  114. }
  115. if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
  116. on.log.Error("Failed to send notification to OpsGenie", "error", err, "body", string(body))
  117. }
  118. return nil
  119. }
  120. func (on *OpsGenieNotifier) closeAlert(evalContext *alerting.EvalContext) error {
  121. on.log.Info("Closing OpsGenie alert", "ruleId", evalContext.Rule.ID, "notification", on.Name)
  122. bodyJSON := simplejson.New()
  123. bodyJSON.Set("source", "Grafana")
  124. body, _ := bodyJSON.MarshalJSON()
  125. cmd := &models.SendWebhookSync{
  126. Url: fmt.Sprintf("%s/alertId-%d/close?identifierType=alias", on.APIUrl, evalContext.Rule.ID),
  127. Body: string(body),
  128. HttpMethod: "POST",
  129. HttpHeader: map[string]string{
  130. "Content-Type": "application/json",
  131. "Authorization": fmt.Sprintf("GenieKey %s", on.APIKey),
  132. },
  133. }
  134. if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
  135. on.log.Error("Failed to send notification to OpsGenie", "error", err, "body", string(body))
  136. return err
  137. }
  138. return nil
  139. }