test http_response_code condition, add human readable condition logging
[god.git] / lib / god / hub.rb
blob3a4ac3e3fb06943b1c84ef495374e51c9cee904b
1 module God
2   
3   class Hub
4     class << self
5       # directory to hold conditions and their corresponding metric
6       # {condition => metric}
7       attr_accessor :directory
8     end
9     
10     self.directory = {}
11     
12     def self.attach(condition, metric)
13       self.directory[condition] = metric
14       
15       case condition
16         when PollCondition
17           Timer.get.schedule(condition, 0)
18         when EventCondition, TriggerCondition
19           condition.register
20       end
21     end
22     
23     def self.detach(condition)
24       self.directory.delete(condition)
25       
26       case condition
27         when PollCondition
28           Timer.get.unschedule(condition)
29         when EventCondition, TriggerCondition
30           condition.deregister
31       end
32     end
33     
34     def self.trigger(condition)
35       case condition
36         when PollCondition
37           self.handle_poll(condition)
38         when EventCondition, TriggerCondition
39           self.handle_event(condition)
40       end
41     end
42     
43     def self.handle_poll(condition)
44       Thread.new do
45         begin
46           metric = self.directory[condition]
47           
48           # it's possible that the timer will trigger an event before it can be cleared
49           # by an exiting metric, in which case it should be ignored
50           unless metric.nil?
51             watch = metric.watch
52             
53             watch.mutex.synchronize do
54               # run the test
55               result = condition.test
56               
57               # log
58               self.log(watch, metric, condition, result)
59               
60               # notify
61               if condition.notify
62                 self.notify(condition, msg)
63               end
64               
65               # after-condition
66               condition.after
67               
68               # get the destination
69               dest = 
70               if result && condition.transition
71                 # condition override
72                 condition.transition
73               else
74                 # regular
75                 metric.destination && metric.destination[result]
76               end
77               
78               # transition or reschedule
79               if dest
80                 # transition
81                 begin
82                   watch.move(dest)
83                 rescue EventRegistrationFailedError
84                   msg = watch.name + ' Event registration failed, moving back to previous state'
85                   Syslog.debug(msg)
86                   LOG.log(watch, :info, msg)
87                   
88                   dest = watch.state
89                   retry
90                 end
91               else
92                 # reschedule
93                 Timer.get.schedule(condition)
94               end
95             end
96           end
97         rescue => e
98           message = format("Unhandled exception (%s): %s\n%s",
99                            e.class, e.message, e.backtrace.join("\n"))
100           Syslog.crit message
101           abort message
102         end
103       end
104     end
105     
106     def self.handle_event(condition)
107       Thread.new do
108         metric = self.directory[condition]
109         
110         unless metric.nil?
111           watch = metric.watch
112           
113           watch.mutex.synchronize do
114             # log
115             self.log(watch, metric, condition, true)
116             
117             # notify
118             if condition.notify
119               self.notify(condition, msg)
120             end
121             
122             # get the destination
123             dest = 
124             if condition.transition
125               # condition override
126               condition.transition
127             else
128               # regular
129               metric.destination && metric.destination[true]
130             end
131             
132             if dest
133               watch.move(dest)
134             end
135           end
136         end
137       end
138     end
139     
140     # helpers
141     
142     def self.log(watch, metric, condition, result)
143       # log info if available
144       if condition.info
145         begin
146           status = 
147           if (metric.destination && metric.destination.keys.size == 2) || result == true
148             "[trigger]"
149           else
150             "[ok]"
151           end
152           
153           Array(condition.info).each do |condition_info|
154             msg = "#{watch.name} #{status} #{condition_info} (#{condition.base_name})"
155             Syslog.debug(msg)
156             LOG.log(watch, :info, msg % [])
157           end
158         rescue Exception => e
159           puts e.message
160           puts e.backtrace.join("\n")
161         end
162       else
163         msg = "#{watch.name} [unknown] (#{condition.base_name})"
164         Syslog.debug(msg)
165         LOG.log(watch, :info, msg % [])
166       end
167       
168       # log
169       msg = watch.name + ' ' + condition.base_name + " [#{result}] " + self.dest_desc(metric, condition)
170       Syslog.debug(msg)
171       LOG.log(watch, :debug, msg)
172     end
173     
174     def self.dest_desc(metric, condition)
175       if condition.transition
176         {true => condition.transition}.inspect
177       else
178         if metric.destination
179           metric.destination.inspect
180         else
181           'none'
182         end
183       end
184     end
185     
186     def self.notify(condition, message)
187       begin
188         spec = Contact.normalize(condition.notify)
189       
190         # resolve contacts
191         resolved_contacts =
192         spec[:contacts].inject([]) do |acc, contact_name_or_group|
193           acc += Array(God.contacts[contact_name_or_group] || God.contact_groups[contact_name_or_group])
194           acc
195         end
196         
197         p resolved_contacts
198         
199         resolved_contacts.each do |c|
200           c.notify(message, Time.now, spec[:priority], spec[:category])
201         end
202       rescue => e
203         puts e.message
204         puts e.backtrace.join("\n")
205       end
206     end
207   end
208