portertech-sensu 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +961 -0
  3. data/MIT-LICENSE.txt +20 -0
  4. data/README.md +65 -0
  5. data/exe/sensu-api +10 -0
  6. data/exe/sensu-client +10 -0
  7. data/exe/sensu-install +195 -0
  8. data/exe/sensu-server +10 -0
  9. data/lib/sensu/api/http_handler.rb +434 -0
  10. data/lib/sensu/api/process.rb +79 -0
  11. data/lib/sensu/api/routes/aggregates.rb +196 -0
  12. data/lib/sensu/api/routes/checks.rb +44 -0
  13. data/lib/sensu/api/routes/clients.rb +171 -0
  14. data/lib/sensu/api/routes/events.rb +86 -0
  15. data/lib/sensu/api/routes/health.rb +45 -0
  16. data/lib/sensu/api/routes/info.rb +37 -0
  17. data/lib/sensu/api/routes/request.rb +44 -0
  18. data/lib/sensu/api/routes/resolve.rb +32 -0
  19. data/lib/sensu/api/routes/results.rb +153 -0
  20. data/lib/sensu/api/routes/settings.rb +23 -0
  21. data/lib/sensu/api/routes/silenced.rb +182 -0
  22. data/lib/sensu/api/routes/stashes.rb +107 -0
  23. data/lib/sensu/api/routes.rb +88 -0
  24. data/lib/sensu/api/utilities/filter_response_content.rb +44 -0
  25. data/lib/sensu/api/utilities/publish_check_request.rb +107 -0
  26. data/lib/sensu/api/utilities/publish_check_result.rb +39 -0
  27. data/lib/sensu/api/utilities/resolve_event.rb +29 -0
  28. data/lib/sensu/api/utilities/servers_info.rb +43 -0
  29. data/lib/sensu/api/utilities/transport_info.rb +43 -0
  30. data/lib/sensu/api/validators/check.rb +55 -0
  31. data/lib/sensu/api/validators/client.rb +35 -0
  32. data/lib/sensu/api/validators/invalid.rb +8 -0
  33. data/lib/sensu/cli.rb +69 -0
  34. data/lib/sensu/client/http_socket.rb +217 -0
  35. data/lib/sensu/client/process.rb +655 -0
  36. data/lib/sensu/client/socket.rb +207 -0
  37. data/lib/sensu/client/utils.rb +53 -0
  38. data/lib/sensu/client/validators/check.rb +53 -0
  39. data/lib/sensu/constants.rb +17 -0
  40. data/lib/sensu/daemon.rb +396 -0
  41. data/lib/sensu/sandbox.rb +19 -0
  42. data/lib/sensu/server/filter.rb +227 -0
  43. data/lib/sensu/server/handle.rb +201 -0
  44. data/lib/sensu/server/mutate.rb +92 -0
  45. data/lib/sensu/server/process.rb +1646 -0
  46. data/lib/sensu/server/socket.rb +54 -0
  47. data/lib/sensu/server/tessen.rb +170 -0
  48. data/lib/sensu/utilities.rb +398 -0
  49. data/lib/sensu.rb +3 -0
  50. data/sensu.gemspec +36 -0
  51. metadata +322 -0
@@ -0,0 +1,396 @@
1
+ require "rubygems"
2
+
3
+ gem "eventmachine", "1.2.7"
4
+
5
+ gem "portertech-sensu-json", "2.2.1"
6
+ gem "portertech-sensu-logger", "1.4.0"
7
+ gem "portertech-sensu-settings", "10.18.0"
8
+ gem "sensu-extension", "1.5.2"
9
+ gem "portertech-sensu-extensions", "1.12.0"
10
+ gem "sensu-transport", "8.3.0"
11
+ gem "portertech-sensu-spawn", "2.6.1"
12
+ gem "sensu-redis", "2.4.0"
13
+
14
+ require "time"
15
+ require "uri"
16
+
17
+ if RUBY_PLATFORM =~ /aix/ || RUBY_PLATFORM =~ /solaris/
18
+ require "em/pure_ruby"
19
+ end
20
+
21
+ require "sensu/json"
22
+ require "sensu/logger"
23
+ require "sensu/settings"
24
+ require "sensu/extensions"
25
+ require "sensu/transport"
26
+ require "sensu/spawn"
27
+ require "sensu/redis"
28
+
29
+ require "sensu/constants"
30
+ require "sensu/utilities"
31
+ require "sensu/cli"
32
+
33
+ module Sensu
34
+ module Daemon
35
+ include Utilities
36
+
37
+ attr_reader :start_time, :settings
38
+
39
+ # Initialize the Sensu process. Set the start time, initial
40
+ # service state, double the maximum number of EventMachine timers,
41
+ # set up the logger, and load settings. This method will load
42
+ # extensions and setup Sensu Spawn if the Sensu process is not the
43
+ # Sensu API. This method can and optionally daemonize the process
44
+ # and/or create a PID file.
45
+ #
46
+ # @param options [Hash]
47
+ def initialize(options={})
48
+ @start_time = Time.now.to_i
49
+ @state = :initializing
50
+ @timers = {:run => []}
51
+ unless EM::reactor_running?
52
+ EM::epoll
53
+ EM::set_max_timers(200000)
54
+ EM::error_handler do |error|
55
+ unexpected_error(error)
56
+ end
57
+ end
58
+ setup_logger(options)
59
+ load_settings(options)
60
+ unless sensu_service_name == "api"
61
+ load_extensions(options)
62
+ setup_spawn
63
+ end
64
+ setup_process(options)
65
+ end
66
+
67
+ # Handle an unexpected error. This method is used for EM global
68
+ # catch-all error handling, accepting an error object. Error
69
+ # handling is opt-in via a configuration option, e.g. `"sensu":
70
+ # {"global_error_handler": true}`. If a user does not opt-in, the
71
+ # provided error will be raised (uncaught). If a user opts-in via
72
+ # configuration, the error will be logged and ignored :itsfine:.
73
+ #
74
+ # @param error [Object]
75
+ def unexpected_error(error)
76
+ if @settings && @settings[:sensu][:global_error_handler]
77
+ backtrace = error.backtrace.join("\n")
78
+ if @logger
79
+ @logger.warn("global catch-all error handling enabled")
80
+ @logger.fatal("unexpected error - please address this immediately", {
81
+ :error => error.to_s,
82
+ :error_class => error.class,
83
+ :backtrace => backtrace
84
+ })
85
+ else
86
+ puts "global catch-all error handling enabled"
87
+ puts "unexpected error - please address this immediately: #{error.to_s}\n#{error.class}\n#{backtrace}"
88
+ end
89
+ else
90
+ raise error
91
+ end
92
+ end
93
+
94
+ # Set up the Sensu logger and its process signal traps for log
95
+ # rotation and debug log level toggling. This method creates the
96
+ # logger instance variable: `@logger`.
97
+ #
98
+ # https://github.com/sensu/sensu-logger
99
+ #
100
+ # @param options [Hash]
101
+ def setup_logger(options={})
102
+ @logger = Logger.get(options)
103
+ @logger.setup_signal_traps
104
+ end
105
+
106
+ # Log setting or extension loading notices, sensitive information
107
+ # is redacted.
108
+ #
109
+ # @param notices [Array] to be logged.
110
+ # @param level [Symbol] to log the notices at.
111
+ def log_notices(notices=[], level=:warn)
112
+ notices.each do |concern|
113
+ message = concern.delete(:message)
114
+ @logger.send(level, message, redact_sensitive(concern))
115
+ end
116
+ end
117
+
118
+ # Determine if the Sensu settings are valid, if there are load or
119
+ # validation errors, and immediately exit the process with the
120
+ # appropriate exit status code. This method is used to determine
121
+ # if the latest configuration changes are valid prior to
122
+ # restarting the Sensu service, triggered by a CLI argument, e.g.
123
+ # `--validate_config`.
124
+ #
125
+ # @param settings [Object]
126
+ def validate_settings!(settings)
127
+ if settings.errors.empty?
128
+ puts "configuration is valid"
129
+ exit
130
+ else
131
+ puts "configuration is invalid"
132
+ puts Sensu::JSON.dump({:errors => @settings.errors}, :pretty => true)
133
+ exit 2
134
+ end
135
+ end
136
+
137
+ # Print the Sensu settings (JSON) to STDOUT and immediately exit
138
+ # the process with the appropriate exit status code. This method
139
+ # is used while troubleshooting configuration issues, triggered by
140
+ # a CLI argument, e.g. `--print_config`. Sensu settings with
141
+ # sensitive values (e.g. passwords) are first redacted.
142
+ #
143
+ # @param settings [Object]
144
+ def print_settings!(settings)
145
+ redacted_settings = redact_sensitive(settings.to_hash)
146
+ @logger.warn("outputting compiled configuration and exiting")
147
+ puts Sensu::JSON.dump(redacted_settings, :pretty => true)
148
+ exit(settings.errors.empty? ? 0 : 2)
149
+ end
150
+
151
+ # Load Sensu settings. This method creates the settings instance
152
+ # variable: `@settings`. If the `validate_config` option is true,
153
+ # this method calls `validate_settings!()` to validate the latest
154
+ # compiled configuration settings and will then exit the process.
155
+ # If the `print_config` option is true, this method calls
156
+ # `print_settings!()` to output the compiled configuration
157
+ # settings and will then exit the process. If there are loading or
158
+ # validation errors, they will be logged (notices), and this
159
+ # method will exit(2) the process.
160
+ #
161
+ #
162
+ # https://github.com/sensu/sensu-settings
163
+ #
164
+ # @param options [Hash]
165
+ def load_settings(options={})
166
+ @settings = Settings.get(options)
167
+ validate_settings!(@settings) if options[:validate_config]
168
+ log_notices(@settings.warnings)
169
+ log_notices(@settings.errors, :fatal)
170
+ print_settings!(@settings) if options[:print_config]
171
+ unless @settings.errors.empty?
172
+ @logger.fatal("SENSU NOT RUNNING!")
173
+ exit 2
174
+ end
175
+ @settings.set_env!
176
+ end
177
+
178
+ # Load Sensu extensions and log any notices. Set the logger and
179
+ # settings for each extension instance. This method creates the
180
+ # extensions instance variable: `@extensions`.
181
+ #
182
+ # https://github.com/sensu/sensu-extensions
183
+ # https://github.com/sensu/sensu-extension
184
+ #
185
+ # @param options [Hash]
186
+ def load_extensions(options={})
187
+ extensions_options = options.merge(:extensions => @settings[:extensions])
188
+ @extensions = Extensions.get(extensions_options)
189
+ log_notices(@extensions.warnings)
190
+ extension_settings = @settings.to_hash.dup
191
+ @extensions.all.each do |extension|
192
+ extension.logger = @logger
193
+ extension.settings = extension_settings
194
+ end
195
+ end
196
+
197
+ # Set up Sensu spawn, creating a worker to create, control, and
198
+ # limit spawned child processes. This method adjusts the
199
+ # EventMachine thread pool size to accommodate the concurrent
200
+ # process spawn limit and other Sensu process operations.
201
+ #
202
+ # https://github.com/sensu/sensu-spawn
203
+ def setup_spawn
204
+ @logger.info("configuring sensu spawn", :settings => @settings[:sensu][:spawn])
205
+ threadpool_size = @settings[:sensu][:spawn][:limit] + 10
206
+ @logger.debug("setting eventmachine threadpool size", :size => threadpool_size)
207
+ EM::threadpool_size = threadpool_size
208
+ Spawn.setup(@settings[:sensu][:spawn])
209
+ end
210
+
211
+ # Manage the current process, optionally daemonize and/or write
212
+ # the current process ID to a PID file.
213
+ #
214
+ # @param options [Hash]
215
+ def setup_process(options)
216
+ daemonize if options[:daemonize]
217
+ write_pid(options[:pid_file]) if options[:pid_file]
218
+ end
219
+
220
+ # Start the Sensu service and set the service state to `:running`.
221
+ # This method will likely be overridden by a subclass. Yield if a
222
+ # block is provided.
223
+ def start
224
+ @state = :running
225
+ yield if block_given?
226
+ end
227
+
228
+ # Pause the Sensu service and set the service state to `:paused`.
229
+ # This method will likely be overridden by a subclass.
230
+ def pause
231
+ @state = :paused
232
+ end
233
+
234
+ # Resume the paused Sensu service and set the service state to
235
+ # `:running`. This method will likely be overridden by a subclass.
236
+ def resume
237
+ @state = :running
238
+ end
239
+
240
+ # Stop the Sensu service and set the service state to `:stopped`.
241
+ # This method will likely be overridden by a subclass. This method
242
+ # should stop the EventMachine event loop.
243
+ def stop
244
+ @state = :stopped
245
+ @logger.warn("stopping reactor")
246
+ EM::stop_event_loop
247
+ end
248
+
249
+ # Set up process signal traps. This method uses the `STOP_SIGNALS`
250
+ # constant to determine which process signals will result in a
251
+ # graceful service stop. A periodic timer must be used to poll for
252
+ # received signals, as Mutex#lock cannot be used within the
253
+ # context of `trap()`.
254
+ def setup_signal_traps
255
+ @signals = []
256
+ STOP_SIGNALS.each do |signal|
257
+ Signal.trap(signal) do
258
+ @signals << signal
259
+ end
260
+ end
261
+ EM::PeriodicTimer.new(1) do
262
+ signal = @signals.shift
263
+ if STOP_SIGNALS.include?(signal)
264
+ @logger.warn("received signal", :signal => signal)
265
+ stop
266
+ end
267
+ end
268
+ end
269
+
270
+ # Set up the Sensu transport connection. Sensu uses a transport
271
+ # API, allowing it to use various message brokers. By default,
272
+ # Sensu will use the built-in "rabbitmq" transport. The Sensu
273
+ # service will stop gracefully in the event of a transport error,
274
+ # and pause/resume in the event of connectivity issues. This
275
+ # method creates the transport instance variable: `@transport`.
276
+ #
277
+ # https://github.com/sensu/sensu-transport
278
+ #
279
+ # @yield [Object] passes initialized and connected Transport
280
+ # connection object to the callback/block.
281
+ def setup_transport
282
+ transport_name = @settings[:transport][:name]
283
+ transport_settings = @settings[transport_name]
284
+ @logger.debug("connecting to transport", {
285
+ :name => transport_name,
286
+ :settings => transport_settings
287
+ })
288
+ Transport.logger = @logger
289
+ Transport.connect(transport_name, transport_settings) do |connection|
290
+ @transport = connection
291
+ @transport.on_error do |error|
292
+ @logger.error("transport connection error", :error => error.to_s)
293
+ if @settings[:transport][:reconnect_on_error]
294
+ @transport.reconnect
295
+ else
296
+ stop
297
+ end
298
+ end
299
+ @transport.before_reconnect do
300
+ unless testing?
301
+ @logger.warn("reconnecting to transport")
302
+ pause
303
+ end
304
+ end
305
+ @transport.after_reconnect do
306
+ @logger.info("reconnected to transport")
307
+ resume
308
+ end
309
+ yield(@transport) if block_given?
310
+ end
311
+ end
312
+
313
+ # Set up the Redis connection. Sensu uses Redis as a data store,
314
+ # to store the client registry, current events, etc. The Sensu
315
+ # service will stop gracefully in the event of a Redis error, and
316
+ # pause/resume in the event of connectivity issues. This method
317
+ # creates the Redis instance variable: `@redis`.
318
+ #
319
+ # https://github.com/sensu/sensu-redis
320
+ #
321
+ # @yield [Object] passes initialized and connected Redis
322
+ # connection object to the callback/block.
323
+ def setup_redis
324
+ @logger.debug("connecting to redis", :settings => @settings[:redis])
325
+ Redis.logger = @logger
326
+ Redis.connect(@settings[:redis]) do |connection|
327
+ @redis = connection
328
+ @redis.on_error do |error|
329
+ @logger.error("redis connection error", :error => error.to_s)
330
+ end
331
+ @redis.before_reconnect do
332
+ unless testing?
333
+ @logger.warn("reconnecting to redis")
334
+ pause
335
+ end
336
+ end
337
+ @redis.after_reconnect do
338
+ @logger.info("reconnected to redis")
339
+ resume
340
+ end
341
+ yield(@redis) if block_given?
342
+ end
343
+ end
344
+
345
+ private
346
+
347
+ # Get the Sensu service name.
348
+ #
349
+ # @return [String] Sensu service name.
350
+ def sensu_service_name
351
+ File.basename($0).split("-").last
352
+ end
353
+
354
+ # Write the current process ID (PID) to a file (PID file). This
355
+ # method will cause the Sensu service to exit (2) if the PID file
356
+ # cannot be written to.
357
+ #
358
+ # @param file [String] to write the current PID to.
359
+ def write_pid(file)
360
+ begin
361
+ File.open(file, "w") do |pid_file|
362
+ pid_file.puts(Process.pid)
363
+ end
364
+ rescue
365
+ @logger.fatal("could not write to pid file", :pid_file => file)
366
+ @logger.fatal("SENSU NOT RUNNING!")
367
+ exit 2
368
+ end
369
+ end
370
+
371
+ # Daemonize the current process. Seed the random number generator,
372
+ # fork (& exit), detach from controlling terminal, ignore SIGHUP,
373
+ # fork (& exit), use root '/' as the current working directory,
374
+ # and close STDIN/OUT/ERR since the process is no longer attached
375
+ # to a terminal.
376
+ def daemonize
377
+ Kernel.srand
378
+ exit if Kernel.fork
379
+ unless Process.setsid
380
+ @logger.fatal("cannot detach from controlling terminal")
381
+ @logger.fatal("SENSU NOT RUNNING!")
382
+ exit 2
383
+ end
384
+ Signal.trap("SIGHUP", "IGNORE")
385
+ exit if Kernel.fork
386
+ Dir.chdir("/")
387
+ ObjectSpace.each_object(IO) do |io|
388
+ unless [STDIN, STDOUT, STDERR].include?(io)
389
+ begin
390
+ io.close unless io.closed?
391
+ rescue; end
392
+ end
393
+ end
394
+ end
395
+ end
396
+ end
@@ -0,0 +1,19 @@
1
+ module Sensu
2
+ module Sandbox
3
+ # Evaluate a Ruby expression within the context of a simple
4
+ # "sandbox", a Proc in a module method. As of Ruby 2.3.0,
5
+ # `$SAFE` no longer supports levels > 1, so its use has been
6
+ # removed from this method. A single value is provided to the
7
+ # "sandbox".
8
+ #
9
+ # @param expression [String] to be evaluated.
10
+ # @param value [Object] to provide the "sandbox" with.
11
+ # @return [Object]
12
+ def self.eval(expression, value=nil)
13
+ result = Proc.new do
14
+ Kernel.eval(expression)
15
+ end
16
+ result.call
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,227 @@
1
+ module Sensu
2
+ module Server
3
+ module Filter
4
+ # Determine if an event handler is silenced.
5
+ #
6
+ # @param handler [Hash] definition.
7
+ # @param event [Hash]
8
+ # @return [TrueClass, FalseClass]
9
+ def handler_silenced?(handler, event)
10
+ event[:silenced] && !handler[:handle_silenced]
11
+ end
12
+
13
+ # Determine if handling is disabled for an event. Check
14
+ # definitions can disable event handling with an attribute,
15
+ # `:handle`, by setting it to `false`.
16
+ #
17
+ # @param event [Hash]
18
+ # @return [TrueClass, FalseClass]
19
+ def handling_disabled?(event)
20
+ event[:check][:handle] == false
21
+ end
22
+
23
+ # Determine if an event with an action should be handled. An
24
+ # event action of `:flapping` indicates that the event state is
25
+ # flapping, and the event should not be handled unless its
26
+ # handler has `:handle_flapping` set to `true`.
27
+ #
28
+ # @param handler [Hash] definition.
29
+ # @param event [Hash]
30
+ # @return [TrueClass, FalseClass]
31
+ def handle_action?(handler, event)
32
+ event[:action] != :flapping ||
33
+ (event[:action] == :flapping && !!handler[:handle_flapping])
34
+ end
35
+
36
+ # Determine if an event with a check severity will be handled.
37
+ # Event handlers can specify the check severities they will
38
+ # handle, using the definition attribute `:severities`. The
39
+ # possible severities are "ok", "warning", "critical", and
40
+ # "unknown". Handler severity filtering is bypassed when the
41
+ # event `:action` is `:resolve` and a previous check history
42
+ # status identifies a severity specified in the handler
43
+ # definition. It's possible for a check history status of 0 to
44
+ # have had the flapping action, so we are unable to consider
45
+ # every past 0 to indicate a resolution.
46
+ #
47
+ # @param handler [Hash] definition.
48
+ # @param event [Hash]
49
+ # @return [TrueClass, FalseClass]
50
+ def handle_severity?(handler, event)
51
+ if handler.has_key?(:severities)
52
+ case event[:action]
53
+ when :resolve
54
+ event[:check][:history].any? do |status|
55
+ severity = SEVERITIES[status.to_i] || "unknown"
56
+ handler[:severities].include?(severity)
57
+ end
58
+ else
59
+ severity = SEVERITIES[event[:check][:status]] || "unknown"
60
+ handler[:severities].include?(severity)
61
+ end
62
+ else
63
+ true
64
+ end
65
+ end
66
+
67
+ # Determine if a filter is to be evoked for the current time. A
68
+ # filter can be configured with a time window defining when it
69
+ # is to be evoked, e.g. Monday through Friday, 9-5.
70
+ #
71
+ # @param filter [Hash] definition.
72
+ # @return [TrueClass, FalseClass]
73
+ def in_filter_time_windows?(filter)
74
+ if filter[:when]
75
+ in_time_windows?(filter[:when])
76
+ else
77
+ true
78
+ end
79
+ end
80
+
81
+ # Determine if an event is filtered by a native filter.
82
+ #
83
+ # @param filter_name [String]
84
+ # @param event [Hash]
85
+ # @yield [filtered] callback/block called with a single
86
+ # parameter to indicate if the event was filtered.
87
+ # @yieldparam filtered [TrueClass,FalseClass] indicating if the
88
+ # event was filtered.
89
+ # @yieldparam filter_name [String] name of the filter being evaluated
90
+ def native_filter(filter_name, event)
91
+ filter = @settings[:filters][filter_name]
92
+ if in_filter_time_windows?(filter)
93
+ matched = attributes_match?(event, filter[:attributes])
94
+ yield(filter[:negate] ? matched : !matched, filter_name)
95
+ else
96
+ yield(false, filter_name)
97
+ end
98
+ end
99
+
100
+ # Determine if an event is filtered by a filter extension.
101
+ #
102
+ # @param filter_name [String]
103
+ # @param event [Hash]
104
+ # @yield [filtered] callback/block called with a single
105
+ # parameter to indicate if the event was filtered.
106
+ # @yieldparam filtered [TrueClass,FalseClass] indicating if the
107
+ # event was filtered.
108
+ # @yieldparam filter_name [String] name of the filter being evaluated
109
+ def extension_filter(filter_name, event)
110
+ extension = @extensions[:filters][filter_name]
111
+ if in_filter_time_windows?(extension.definition)
112
+ extension.safe_run(event) do |output, status|
113
+ yield(status == 0, filter_name)
114
+ end
115
+ else
116
+ yield(false, filter_name)
117
+ end
118
+ end
119
+
120
+ # Determine if an event is filtered by an event filter, native
121
+ # or extension. This method first checks for the existence of a
122
+ # native filter, then checks for an extension if a native filter
123
+ # is not defined. The provided callback is called with a single
124
+ # parameter, indicating if the event was filtered by a filter.
125
+ # If a filter does not exist for the provided name, the event is
126
+ # not filtered.
127
+ #
128
+ # @param filter_name [String]
129
+ # @param event [Hash]
130
+ # @yield [filtered] callback/block called with a single
131
+ # parameter to indicate if the event was filtered.
132
+ # @yieldparam filtered [TrueClass,FalseClass] indicating if the
133
+ # event was filtered.
134
+ # @yieldparam filter_name [String] name of the filter being evaluated
135
+ def event_filter(filter_name, event)
136
+ case
137
+ when @settings.filter_exists?(filter_name)
138
+ native_filter(filter_name, event) do |filtered|
139
+ yield(filtered, filter_name)
140
+ end
141
+ when @extensions.filter_exists?(filter_name)
142
+ extension_filter(filter_name, event) do |filtered|
143
+ yield(filtered, filter_name)
144
+ end
145
+ else
146
+ @logger.error("unknown filter", :filter_name => filter_name)
147
+ yield(false, filter_name)
148
+ end
149
+ end
150
+
151
+ # Determine if an event is filtered for a handler. If a handler
152
+ # specifies one or more filters, via `:filters` or `:filter`,
153
+ # the `event_filter()` method is called for each of them. If any
154
+ # of the filters return `true`, the event is filtered for the
155
+ # handler. If no filters are defined in the handler definition,
156
+ # the event is not filtered.
157
+ #
158
+ # @param handler [Hash] definition.
159
+ # @param event [Hash]
160
+ # @yield [filtered] callback/block called with a single
161
+ # parameter to indicate if the event was filtered.
162
+ # @yieldparam filtered [TrueClass,FalseClass] indicating if the
163
+ # event was filtered.
164
+ def event_filtered?(handler, event)
165
+ if handler.has_key?(:filters) || handler.has_key?(:filter)
166
+ filter_list = Array(handler[:filters] || handler[:filter]).dup
167
+ filter = Proc.new do |filter_list|
168
+ filter_name = filter_list.shift
169
+ if filter_name.nil?
170
+ yield(false)
171
+ else
172
+ event_filter(filter_name, event) do |filtered|
173
+ filtered ? yield(true, filter_name) : EM.next_tick { filter.call(filter_list) }
174
+ end
175
+ end
176
+ end
177
+ filter.call(filter_list)
178
+ else
179
+ yield(false)
180
+ end
181
+ end
182
+
183
+ # Attempt to filter an event for a handler. This method will
184
+ # check to see if handling is disabled, if the event action is
185
+ # handled, if the event check severity is handled, if the
186
+ # handler is subdued, and if the event is filtered by any of the
187
+ # filters specified in the handler definition.
188
+ #
189
+ # @param handler [Hash] definition.
190
+ # @param event [Hash]
191
+ # @yield [event] callback/block called if the event has not been
192
+ # filtered.
193
+ # @yieldparam event [Hash]
194
+ def filter_event(handler, event)
195
+ handler_info = case handler[:type]
196
+ when "extension" then handler.definition
197
+ else handler
198
+ end
199
+ details = {:handler => handler_info, :event => event}
200
+ filter_message = case
201
+ when handling_disabled?(event)
202
+ "event handling disabled for event"
203
+ when !handle_action?(handler, event)
204
+ "handler does not handle action"
205
+ when !handle_severity?(handler, event)
206
+ "handler does not handle event severity"
207
+ when handler_silenced?(handler, event)
208
+ "handler is silenced"
209
+ end
210
+ if filter_message
211
+ @logger.info(filter_message, details)
212
+ @in_progress[:events] -= 1 if @in_progress
213
+ else
214
+ event_filtered?(handler, event) do |filtered, filter_name|
215
+ unless filtered
216
+ yield(event)
217
+ else
218
+ details[:filter] = filter_name
219
+ @logger.info("event was filtered", details)
220
+ @in_progress[:events] -= 1 if @in_progress
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end