1 | _ = require 'lodash'
|
2 | async = require 'async'
|
3 | request = require 'request'
|
4 | debug = (require 'debug')('octoblu-flow-canary:slack')
|
5 |
|
6 | class Slack
|
7 |
|
8 | constructor: ({@CANARY_UPDATE_INTERVAL,@CANARY_HEALTH_CHECK_MAX_DIFF}={}) ->
|
9 | @slackNotifications = {}
|
10 |
|
11 | @SLACK_CHANNEL_URL = process.env.SLACK_CHANNEL_URL
|
12 | @SLACK_EMERGENCY_CHANNEL = process.env.SLACK_EMERGENCY_CHANNEL
|
13 | @SLACK_EMERGENCY_CHANNEL ?= "#performance-problems"
|
14 | throw new Error('SLACK_CHANNEL_URL must be defined') unless @SLACK_CHANNEL_URL
|
15 | @startTime = Date.now()
|
16 |
|
17 | sendSlackNotifications: (stats, callback) =>
|
18 | notifications = []
|
19 | update = false
|
20 | ded = false
|
21 |
|
22 | if !@slackNotifications['lastNotify']
|
23 | lowerResponseTime = (@CANARY_UPDATE_INTERVAL - @CANARY_HEALTH_CHECK_MAX_DIFF) / 1000
|
24 | upperResponseTime = (@CANARY_UPDATE_INTERVAL + @CANARY_HEALTH_CHECK_MAX_DIFF) / 1000
|
25 | notifications.push @curryPostSlackNotification {
|
26 | attachments: [{color:"good",text:"The flow-canary is alive! Expected response time is between #{lowerResponseTime} and #{upperResponseTime} seconds"}]
|
27 | }
|
28 | @slackNotifications['lastNotify'] = Date.now()
|
29 |
|
30 | @slackNotifications['lastError'] ?= 0
|
31 | stats.errors ?= []
|
32 | _.each stats.errors.reverse(), (errorInfo) =>
|
33 | if @slackNotifications['lastError'] < errorInfo.time
|
34 | @slackNotifications['lastError'] = errorInfo.time
|
35 | notifications.push @curryPostSlackNotification {
|
36 | icon_emoji: ':bird:'
|
37 | username: 'flow-canary-wut'
|
38 | attachments: [{color:"warning",text:"Error: #{errorInfo.url}"}]
|
39 | }
|
40 |
|
41 | _.forIn stats.flows, (flow, flowId) =>
|
42 | @slackNotifications[flowId] ?= true
|
43 |
|
44 | @slackNotifications['lastFailure'] ?= 0
|
45 | flow.failures ?= []
|
46 | _.each flow.failures.reverse(), (errorInfo) =>
|
47 | if @slackNotifications['lastFailure'] < errorInfo.time
|
48 | update = true
|
49 | ded = true
|
50 | @slackNotifications['lastFailure'] = errorInfo.time
|
51 | @slackNotifications[flowId] = false
|
52 | timeDiffInSeconds = errorInfo.timeDiff / 1000
|
53 | notifications.push @curryPostSlackNotification {
|
54 | icon_emoji: ':skull:'
|
55 | username: 'flow-canary-ded'
|
56 | attachments: [
|
57 | {
|
58 | color:"danger",
|
59 | text:"Flow #{flow.name} (#{flowId}) failed because it took #{timeDiffInSeconds} seconds to respond"
|
60 | }
|
61 | ]
|
62 | }
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 | if flow.passing and !@slackNotifications[flowId]
|
78 | @slackNotifications[flowId] = true
|
79 | update = true
|
80 | notifications.push @curryPostSlackNotification {
|
81 | attachments: [{color:"good",text:"Flow #{flow.name} (#{flowId}) is now passing"}]
|
82 | }
|
83 |
|
84 | if update
|
85 | failingFlows = ""
|
86 | failCount = 0
|
87 | _.each stats.flows, (flowInfo) =>
|
88 | if !flowInfo.passing
|
89 | failingFlows += ">#{flowInfo.name}< "
|
90 | failCount += 1
|
91 | if failCount == 0
|
92 | notifications.push @curryPostSlackNotification {
|
93 | attachments: [{color:"good",text: "All flows are now passing"}]
|
94 | }
|
95 | if failCount == stats.flows?.length && (Date.now()-@startTime) > 5*60*1000
|
96 | notifications.push @curryPostSlackNotification {
|
97 | icon_emoji: ':skull:'
|
98 | username: 'flow-canary-ded'
|
99 | attachments: [{color:"danger",text: "All flows are failing!"}]
|
100 | }, true
|
101 | else if ded
|
102 | notifications.push @curryPostSlackNotification {
|
103 | icon_emoji: ':skull:'
|
104 | username: 'flow-canary-ded'
|
105 | attachments: [{color:"danger",text:"#{failCount} flows are failing: #{failingFlows}"}]
|
106 | }
|
107 | else if failCount != 0
|
108 | notifications.push @curryPostSlackNotification {
|
109 | attachments: [{color:"danger",text:"#{failCount} flows are failing: #{failingFlows}"}]
|
110 | }
|
111 |
|
112 | lastUpdate = Date.now() - @slackNotifications['lastNotify']
|
113 | if !stats.passing and lastUpdate >= 60*60*1000
|
114 | notifications.push @curryPostSlackNotification {
|
115 | icon_emoji: ':skull:'
|
116 | username: 'flow-canary-ded'
|
117 | attachments: [{color:"danger",text:"Flow-canary is still dead!"}]
|
118 | }
|
119 |
|
120 | @slackNotifications['lastNotify'] = Date.now()
|
121 | async.series notifications, callback
|
122 |
|
123 | curryPostSlackNotification: (payload, emergency)=>
|
124 | defaultPayload =
|
125 | username: 'flow-canary'
|
126 | icon_emoji: ':baby_chick:'
|
127 |
|
128 | channel = @SLACK_EMERGENCY_CHANNEL if emergency
|
129 |
|
130 | options =
|
131 | uri: @SLACK_CHANNEL_URL
|
132 | channel: channel
|
133 | method: 'POST'
|
134 | body: _.merge defaultPayload, payload
|
135 | json: true
|
136 |
|
137 | return (callback) =>
|
138 | debug JSON.stringify options
|
139 | request options, (error, response, body) =>
|
140 | console.error 'Slack Error', error if error?
|
141 | debug {body}
|
142 | callback()
|
143 |
|
144 | module.exports = Slack
|