github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/alerts/alertsHandler/notificationHandler.go (about) 1 package alertsHandler 2 3 /* 4 Copyright 2023. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "net/http" 25 "net/smtp" 26 "strconv" 27 "time" 28 29 "github.com/slack-go/slack" 30 31 "github.com/siglens/siglens/pkg/alerts/alertutils" 32 "github.com/siglens/siglens/pkg/config" 33 34 log "github.com/sirupsen/logrus" 35 ) 36 37 func NotifyAlertHandlerRequest(alertID string) error { 38 if alertID == "" { 39 log.Errorf("NotifyAlertHandlerRequest: Missing alert_id") 40 return errors.New("Alert ID is empty") 41 } 42 cooldownOver, err := isCooldownOver(alertID) 43 if err != nil { 44 log.Errorf("NotifyAlertHandlerRequest:Error checking cooldown period for alert id- %s, err=%v", alertID, err) 45 return err 46 } 47 if !cooldownOver { 48 return nil 49 } 50 silenceMinutesOver, err := isSilenceMinutesOver(alertID) 51 if err != nil { 52 log.Errorf("NotifyAlertHandlerRequest:Error checking silence period for alert id- %s, err=%v", alertID, err) 53 return err 54 } 55 if !silenceMinutesOver { 56 return nil 57 } 58 contact_id, message, subject, err := processGetContactDetails(alertID) 59 if err != nil { 60 log.Errorf("NotifyAlertHandlerRequest:Error retrieving contact and message for alert id- %s, err=%v", alertID, err) 61 return err 62 } 63 emailIDs, channelIDs, webhooks, err := processGetEmailAndChannelID(contact_id) 64 if err != nil { 65 log.Errorf("NotifyAlertHandlerRequest:Error retrieving emails or channelIds of slack for contact_id- %s and alert id- %s, err=%v", contact_id, alertID, err) 66 return err 67 } 68 emailSent := false 69 slackSent := false 70 webhookSent := false 71 if len(emailIDs) > 0 { 72 for _, emailID := range emailIDs { 73 err = sendAlertEmail(emailID, subject, message) 74 if err != nil { 75 log.Errorf("NotifyAlertHandlerRequest: Error sending email to- %s for alert id- %s, err=%v", emailID, alertID, err) 76 } else { 77 emailSent = true 78 } 79 } 80 } 81 if len(channelIDs) > 0 { 82 for _, channelID := range channelIDs { 83 err = sendSlack(subject, message, channelID) 84 if err != nil { 85 log.Errorf("NotifyAlertHandlerRequest: Error sending Slack message to channelID- %v for alert id- %v, err=%v", channelID, alertID, err) 86 } else { 87 slackSent = true 88 } 89 } 90 } 91 if len(webhooks) > 0 { 92 for _, webhook := range webhooks { 93 err = sendWebhooks(webhook, subject, message) 94 if err != nil { 95 log.Errorf("NotifyAlertHandlerRequest: Error sending Webhook message to webhook- %s for alert id- %s, err=%v", webhook, alertID, err) 96 } else { 97 webhookSent = true 98 } 99 } 100 } 101 102 if !emailSent && !slackSent && !webhookSent { 103 return errors.New("Neither emails or slack message or webhook sent for this notification") 104 105 } 106 107 err = processUpdateLastSentTime(alertID) 108 if err != nil { 109 log.Errorf("NotifyAlertHandlerRequest:Error updating last sent time for alert_id- %s, err=%v", alertID, err) 110 return err 111 } 112 return nil 113 } 114 115 func sendAlertEmail(emailID, subject, message string) error { 116 host, port, senderEmail, senderPassword := config.GetEmailConfig() 117 auth := smtp.PlainAuth("", senderEmail, senderPassword, host) 118 body := "To: " + emailID + "\r\n" + 119 "Subject: " + subject + "\r\n" + 120 "\r\n" + 121 message + "\r\n" 122 err := smtp.SendMail(host+":"+strconv.Itoa(port), auth, senderEmail, []string{emailID}, []byte(body)) 123 return err 124 } 125 func sendWebhooks(webhookUrl, subject, message string) error { 126 webhookBody := alertutils.WebhookBody{ 127 Receiver: "My Super Webhook", 128 Status: "firing", 129 Title: subject, 130 Body: message, 131 Alerts: []alertutils.Alert{ 132 { 133 Status: "firing", 134 }, 135 }, 136 } 137 138 data, _ := json.Marshal(webhookBody) 139 140 r, err := http.NewRequest("POST", webhookUrl, bytes.NewBuffer(data)) 141 if err != nil { 142 log.Errorf("Error creating request: %v", err) 143 } 144 145 r.Header.Add("Content-Type", "application/json") 146 client := &http.Client{} 147 _, err1 := client.Do(r) 148 if err1 != nil { 149 log.Errorf("Error sending request: %v", err) 150 } 151 return err 152 } 153 154 func isSilenceMinutesOver(alertID string) (bool, error) { 155 silenceMinutes, lastSendTime, err := processGetSilenceMinutesRequest(alertID) 156 157 if lastSendTime.IsZero() { 158 return true, nil 159 } 160 161 if err != nil { 162 return true, err 163 } 164 165 currentTimeUTC := time.Now().UTC() 166 lastSendTimeUTC := lastSendTime.UTC() 167 silenceMinutesUTC := time.Duration(silenceMinutes) * time.Minute 168 if currentTimeUTC.Sub(lastSendTimeUTC) >= silenceMinutesUTC { 169 return true, nil 170 } 171 return false, nil 172 } 173 174 func isCooldownOver(alertID string) (bool, error) { 175 cooldownMinutes, lastSendTime, err := processGetCooldownRequest(alertID) 176 177 if lastSendTime.IsZero() { 178 return true, nil 179 } 180 181 if err != nil { 182 return false, err 183 } 184 185 currentTimeUTC := time.Now().UTC() 186 lastSendTimeUTC := lastSendTime.UTC() 187 cooldownDuration := time.Duration(cooldownMinutes) * time.Minute 188 if currentTimeUTC.Sub(lastSendTimeUTC) >= cooldownDuration { 189 return true, nil 190 } 191 return false, nil 192 } 193 194 func sendSlack(alertName string, message string, channel alertutils.SlackTokenConfig) error { 195 196 channelID := channel.ChannelId 197 token := channel.SlToken 198 alert := fmt.Sprintf("Alert Name : '%s'", alertName) 199 client := slack.New(token, slack.OptionDebug(false)) 200 201 attachment := slack.Attachment{ 202 Pretext: alert, 203 Text: message, 204 Color: "#FF0000", 205 Fields: []slack.AttachmentField{ 206 { 207 Title: "Date", 208 Value: time.Now().String(), 209 }, 210 }, 211 } 212 _, _, err := client.PostMessage( 213 channelID, 214 slack.MsgOptionText("New message from Alert System", false), 215 slack.MsgOptionAttachments(attachment), 216 ) 217 return err 218 } 219 220 func processGetCooldownRequest(alert_id string) (uint64, time.Time, error) { 221 period, last_time, err := databaseObj.GetCoolDownDetails(alert_id) 222 if err != nil { 223 log.Errorf("ProcessGetCooldownRequest:Error getting cooldown details for alert id- %s err=%v", alert_id, err) 224 return 0, time.Time{}, err 225 } 226 return period, last_time, nil 227 } 228 229 func processGetSilenceMinutesRequest(alert_id string) (uint64, time.Time, error) { 230 alertDataObj, err := databaseObj.GetAlert(alert_id) 231 if err != nil { 232 log.Errorf("ProcessGetSilenceMinutesRequest:Error getting alert details for alert id- %s err=%v", alert_id, err) 233 return 0, time.Time{}, err 234 } 235 236 _, last_time, err := databaseObj.GetCoolDownDetails(alert_id) 237 if err != nil { 238 log.Errorf("ProcessGetSilenceMinutesRequest:Error getting cooldown details for alert id- %s err=%v", alert_id, err) 239 return 0, time.Time{}, err 240 } 241 242 return alertDataObj.SilenceMinutes, last_time, nil 243 } 244 func processGetContactDetails(alert_id string) (string, string, string, error) { 245 id, message, subject, err := databaseObj.GetContactDetails(alert_id) 246 if err != nil { 247 log.Errorf("ProcessGetContactDetails: Error getting contact details for alert id- %s, err=%v", alert_id, err) 248 return "", "", "", err 249 } 250 return id, message, subject, nil 251 } 252 253 func processGetEmailAndChannelID(contact_id string) ([]string, []alertutils.SlackTokenConfig, []string, error) { 254 emails, slacks, webhook, err := databaseObj.GetEmailAndChannelID(contact_id) 255 if err != nil { 256 log.Errorf("ProcessGetEmailAndChannelID: Error in getting emails and channel_ids for contact_id- %s, err=%v", contact_id, err) 257 return nil, nil, nil, err 258 } 259 260 return emails, slacks, webhook, nil 261 } 262 263 func processUpdateLastSentTime(alert_id string) error { 264 err := databaseObj.UpdateLastSentTime(alert_id) 265 if err != nil { 266 log.Errorf("ProcessUpdateLastSentTime: Unable to update last_sent_time for alert_id- %s, err=%v", alert_id, err) 267 return err 268 } 269 return nil 270 }