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  }