Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/detect broken contacts #615

Merged
merged 10 commits into from
Mar 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package moira

//SenderBrokenContactError means than sender has no way to send message to contact.
// Maybe receive contact was deleted, blocked or archived.
type SenderBrokenContactError struct {
SenderError error
}
litleleprikon marked this conversation as resolved.
Show resolved Hide resolved

func NewSenderBrokenContactError(senderError error) SenderBrokenContactError {
return SenderBrokenContactError{
SenderError: senderError,
}
}

func (e SenderBrokenContactError) Error() string {
return e.SenderError.Error()
}
10 changes: 7 additions & 3 deletions notifier/log_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ func getLogWithPackageContext(log *moira.Logger, pkg *NotificationPackage, confi
logger := (*log).Clone().
String(moira.LogFieldNameContactID, pkg.Contact.ID).
String(moira.LogFieldNameContactType, pkg.Contact.Type).
String(moira.LogFieldNameContactValue, pkg.Contact.Value).
String(moira.LogFieldNameTriggerID, pkg.Trigger.ID).
String(moira.LogFieldNameTriggerName, pkg.Trigger.Name)
String(moira.LogFieldNameContactValue, pkg.Contact.Value)
if pkg.Trigger.ID != "" { // note: test notification without trigger info
logger.
String(moira.LogFieldNameTriggerID, pkg.Trigger.ID).
String(moira.LogFieldNameTriggerName, pkg.Trigger.Name)
}

SetLogLevelByConfig(config.LogContactsToLevel, pkg.Contact.ID, &logger)
return logger
}
Expand Down
10 changes: 8 additions & 2 deletions notifier/notifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,14 @@ func (notifier *StandardNotifier) runSender(sender moira.Sender, ch chan Notific
metric.Mark(1)
}
} else {
log.Errorf("Cannot send notification: %s", err.Error())
notifier.resend(&pkg, err.Error())
switch e := err.(type) {
case moira.SenderBrokenContactError:
log.Errorf("Cannot send to broken contact: %s", e.Error())

default:
log.Errorf("Cannot send notification: %s", err.Error())
notifier.resend(&pkg, err.Error())
}
}
}
}
25 changes: 25 additions & 0 deletions notifier/notifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,31 @@ func TestFailSendEvent(t *testing.T) {
time.Sleep(time.Second * 2)
}

func TestNoResendForSendToBrokenContact(t *testing.T) {
litleleprikon marked this conversation as resolved.
Show resolved Hide resolved
if testing.Short() {
t.Skip("skipping test in short mode.")
}

configureNotifier(t)
defer afterTest()

var eventsData moira.NotificationEvents = []moira.NotificationEvent{event}

pkg := NotificationPackage{
Events: eventsData,
Contact: moira.ContactData{
Type: "test",
},
}
sender.EXPECT().SendEvents(eventsData, pkg.Contact, pkg.Trigger, plots, pkg.Throttled).
Return(moira.NewSenderBrokenContactError(fmt.Errorf("some sender reason")))

var wg sync.WaitGroup
notif.Send(&pkg, &wg)
wg.Wait()
time.Sleep(time.Second * 2)
}

func TestTimeout(t *testing.T) {
configureNotifier(t)
var wg sync.WaitGroup
Expand Down
13 changes: 12 additions & 1 deletion senders/slack/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ const (
testEmoji = ":moira-state-test:"

messageMaxCharacters = 4000

//see errors https://api.slack.com/methods/chat.postMessage
ErrorTextChannelArchived = "is_archived"
ErrorTextChannelNotFound = "channel_not_found"
ErrorTextNotInChannel = "not_in_channel"
)

var stateEmoji = map[moira.State]string{
Expand Down Expand Up @@ -185,7 +190,13 @@ func (sender *Sender) sendMessage(message string, contact string, triggerID stri
sender.logger.Debugf("Calling slack with message body %s", message)
channelID, threadTimestamp, err := sender.client.PostMessage(contact, slack.MsgOptionText(message, false), slack.MsgOptionPostMessageParameters(params))
if err != nil {
return channelID, threadTimestamp, fmt.Errorf("failed to send %s event message to slack [%s]: %s", triggerID, contact, err.Error())
errorText := err.Error()
if errorText == ErrorTextChannelArchived || errorText == ErrorTextNotInChannel ||
errorText == ErrorTextChannelNotFound {
return channelID, threadTimestamp, moira.NewSenderBrokenContactError(err)
}
return channelID, threadTimestamp, fmt.Errorf("failed to send %s event message to slack [%s]: %s",
triggerID, contact, errorText)
}
return channelID, threadTimestamp, nil
}
Expand Down
32 changes: 26 additions & 6 deletions senders/telegram/send.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ func (sender *Sender) SendEvents(events moira.NotificationEvents, contact moira.
sender.logger.Debugf("Calling telegram api with chat_id %s and message body %s", contact.Value, message)
chat, err := sender.getChat(contact.Value)
if err != nil {
return err
return checkBrokenContactError(sender.logger, err)
}
if err := sender.talk(chat, message, plots, msgType); err != nil {
return fmt.Errorf("failed to send message to telegram contact %s: %s. ", contact.Value, err)
return checkBrokenContactError(sender.logger, err)
}
return nil
}
Expand Down Expand Up @@ -116,17 +116,37 @@ func (sender *Sender) getChat(username string) (*telebot.Chat, error) {
// talk processes one talk
func (sender *Sender) talk(chat *telebot.Chat, message string, plots [][]byte, messageType messageType) error {
if messageType == Album {
sender.logger.Debug("talk as album")
return sender.sendAsAlbum(chat, plots, message)
}
sender.logger.Debug("talk as send message")
return sender.sendAsMessage(chat, message)
}

func (sender *Sender) sendAsMessage(chat *telebot.Chat, message string) error {
_, err := sender.bot.Send(chat, message)
if err != nil {
return fmt.Errorf("can't send event message [%s] to %v: %s", message, chat.ID, err.Error())
sender.logger.Debugf("can't send event message [%s] to %v: %s", message, chat.ID, err.Error())
}
return nil
return err
}

func checkBrokenContactError(logger moira.Logger, err error) error {
logger.Debug("Check broken contact")
if err == nil {
return nil
}
if e, ok := err.(*telebot.APIError); ok {
logger.Debug("It's telebot.APIError from talk(): code = %d, msg = %s, desc = %s", e.Code, e.Message, e.Description)
if e.Code == telebot.ErrUnauthorized.Code { // all forbid errors
return moira.NewSenderBrokenContactError(err)
}
}
if strings.HasPrefix(err.Error(), "failed to get username uuid") {
logger.Debug("It's error from getChat(): ", err)
return moira.NewSenderBrokenContactError(err)
}
return err
}

func prepareAlbum(plots [][]byte, caption string) telebot.Album {
Expand All @@ -144,9 +164,9 @@ func (sender *Sender) sendAsAlbum(chat *telebot.Chat, plots [][]byte, caption st

_, err := sender.bot.SendAlbum(chat, album)
if err != nil {
return fmt.Errorf("can't send event plots to %v: %s", chat.ID, err.Error())
sender.logger.Debugf("can't send event plots to %v: %s", chat.ID, err.Error())
}
return nil
return err
}

func getMessageType(plots [][]byte) messageType {
Expand Down