rabbit_feed 2.4.4 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +81 -0
- data/README.md +39 -5
- data/Rakefile +3 -19
- data/bin/rabbit_feed +0 -1
- data/example/non_rails_app/Gemfile.lock +29 -32
- data/example/non_rails_app/Rakefile +1 -1
- data/example/non_rails_app/bin/benchmark +3 -3
- data/example/non_rails_app/lib/non_rails_app/event_handler.rb +1 -1
- data/example/non_rails_app/spec/lib/non_rails_app/event_handler_spec.rb +3 -4
- data/example/non_rails_app/spec/lib/non_rails_app/event_routing_spec.rb +3 -3
- data/example/rails_app/Gemfile +4 -16
- data/example/rails_app/Gemfile.lock +131 -137
- data/example/rails_app/app/assets/javascripts/application.js +0 -1
- data/example/rails_app/app/controllers/application_controller.rb +0 -3
- data/example/rails_app/app/controllers/beavers_controller.rb +14 -15
- data/example/rails_app/bin/rails +1 -1
- data/example/rails_app/config/environments/development.rb +1 -1
- data/example/rails_app/config/environments/test.rb +2 -2
- data/example/rails_app/config/initializers/cookies_serializer.rb +1 -1
- data/example/rails_app/config/unicorn.rb +1 -1
- data/example/rails_app/config.ru +1 -1
- data/example/rails_app/db/schema.rb +5 -7
- data/example/rails_app/lib/event_handler.rb +1 -1
- data/example/rails_app/spec/controllers/beavers_controller_spec.rb +9 -10
- data/example/rails_app/spec/event_routing_spec.rb +1 -2
- data/example/rails_app/test/controllers/beavers_controller_test.rb +12 -12
- data/lib/dsl.rb +4 -4
- data/lib/rabbit_feed/client.rb +17 -23
- data/lib/rabbit_feed/configuration.rb +10 -9
- data/lib/rabbit_feed/connection.rb +3 -3
- data/lib/rabbit_feed/console_consumer.rb +22 -24
- data/lib/rabbit_feed/consumer.rb +2 -2
- data/lib/rabbit_feed/consumer_connection.rb +21 -22
- data/lib/rabbit_feed/event.rb +8 -28
- data/lib/rabbit_feed/event_definitions.rb +14 -15
- data/lib/rabbit_feed/event_routing.rb +26 -27
- data/lib/rabbit_feed/json_log_formatter.rb +1 -1
- data/lib/rabbit_feed/producer.rb +13 -13
- data/lib/rabbit_feed/producer_connection.rb +8 -9
- data/lib/rabbit_feed/testing_support/rspec_matchers/publish_event.rb +52 -89
- data/lib/rabbit_feed/testing_support/test_rabbit_feed_consumer.rb +1 -2
- data/lib/rabbit_feed/testing_support/testing_helpers.rb +0 -1
- data/lib/rabbit_feed/testing_support.rb +5 -6
- data/lib/rabbit_feed/version.rb +1 -1
- data/lib/rabbit_feed.rb +12 -13
- data/rabbit_feed.gemspec +16 -14
- data/run_benchmark +4 -3
- data/run_example +1 -1
- data/spec/features/step_definitions/connectivity_steps.rb +6 -9
- data/spec/lib/rabbit_feed/client_spec.rb +8 -9
- data/spec/lib/rabbit_feed/configuration_spec.rb +20 -23
- data/spec/lib/rabbit_feed/console_consumer_spec.rb +11 -13
- data/spec/lib/rabbit_feed/consumer_connection_spec.rb +26 -28
- data/spec/lib/rabbit_feed/event_definitions_spec.rb +31 -31
- data/spec/lib/rabbit_feed/event_routing_spec.rb +35 -62
- data/spec/lib/rabbit_feed/event_spec.rb +40 -87
- data/spec/lib/rabbit_feed/producer_connection_spec.rb +11 -7
- data/spec/lib/rabbit_feed/producer_spec.rb +16 -19
- data/spec/lib/rabbit_feed/testing_support/rspec_matchers/publish_event_spec.rb +82 -87
- data/spec/lib/rabbit_feed/testing_support/testing_helper_spec.rb +2 -2
- data/spec/spec_helper.rb +4 -10
- metadata +67 -45
- data/example/rails_app/README.rdoc +0 -28
data/lib/rabbit_feed/client.rb
CHANGED
@@ -10,30 +10,27 @@ module RabbitFeed
|
|
10
10
|
require_path: '.',
|
11
11
|
config_file: 'config/rabbit_feed.yml',
|
12
12
|
logfile: 'log/rabbit_feed.log',
|
13
|
-
pidfile: 'tmp/pids/rabbit_feed.pid'
|
14
|
-
}
|
13
|
+
pidfile: 'tmp/pids/rabbit_feed.pid'
|
14
|
+
}.freeze
|
15
15
|
DEFAULTS.freeze
|
16
16
|
|
17
17
|
attr_reader :command, :options
|
18
18
|
validates_presence_of :command, :options
|
19
|
-
validates :command, inclusion: { in: %w(consume produce shutdown console), message:
|
19
|
+
validates :command, inclusion: { in: %w(consume produce shutdown console), message: '%{value} is not a valid command' }
|
20
20
|
validate :log_file_path_exists
|
21
21
|
validate :config_file_exists
|
22
22
|
validate :require_path_valid, unless: :console?
|
23
23
|
validate :pidfile_path_exists, if: :daemonize?
|
24
24
|
validate :environment_specified
|
25
25
|
|
26
|
-
def initialize
|
26
|
+
def initialize(arguments = ARGV)
|
27
27
|
@command = arguments[0]
|
28
28
|
@options = parse_options arguments
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
set_configuration
|
35
|
-
load_dependancies unless console?
|
36
|
-
end
|
29
|
+
return if shutdown?
|
30
|
+
validate!
|
31
|
+
set_logging
|
32
|
+
set_configuration
|
33
|
+
load_dependancies unless console?
|
37
34
|
end
|
38
35
|
|
39
36
|
def run
|
@@ -43,25 +40,24 @@ module RabbitFeed
|
|
43
40
|
private
|
44
41
|
|
45
42
|
def validate!
|
46
|
-
raise Error
|
43
|
+
raise Error, errors.messages if invalid?
|
47
44
|
end
|
48
45
|
|
49
46
|
def log_file_path_exists
|
50
|
-
errors.add(:options, "log file path not found: '#{options[:logfile]}', specify this using the --logfile option") unless Dir.
|
47
|
+
errors.add(:options, "log file path not found: '#{options[:logfile]}', specify this using the --logfile option") unless Dir.exist?(File.dirname(options[:logfile]))
|
51
48
|
end
|
52
49
|
|
53
50
|
def config_file_exists
|
54
|
-
errors.add(:options, "configuration file not found: '#{options[:config_file]}', specify this using the --config option") unless File.
|
51
|
+
errors.add(:options, "configuration file not found: '#{options[:config_file]}', specify this using the --config option") unless File.exist?(options[:config_file])
|
55
52
|
end
|
56
53
|
|
57
54
|
def require_path_valid
|
58
|
-
|
59
|
-
|
60
|
-
end
|
55
|
+
return unless require_rails? && !File.exist?("#{options[:require_path]}/config/application.rb")
|
56
|
+
errors.add(:options, 'point rabbit_feed to a Rails 3/4 application or a Ruby file to load your worker classes with --require')
|
61
57
|
end
|
62
58
|
|
63
59
|
def pidfile_path_exists
|
64
|
-
errors.add(:options, "pid file path not found: '#{options[:pidfile]}', specify this using the --pidfile option") unless Dir.
|
60
|
+
errors.add(:options, "pid file path not found: '#{options[:pidfile]}', specify this using the --pidfile option") unless Dir.exist?(File.dirname(options[:pidfile]))
|
65
61
|
end
|
66
62
|
|
67
63
|
def environment_specified
|
@@ -145,11 +141,10 @@ module RabbitFeed
|
|
145
141
|
File.directory?(options[:require_path])
|
146
142
|
end
|
147
143
|
|
148
|
-
def parse_options
|
144
|
+
def parse_options(argv)
|
149
145
|
opts = {}
|
150
146
|
|
151
147
|
parser = OptionParser.new do |o|
|
152
|
-
|
153
148
|
o.on '-a', '--application VAL', 'Name of the application' do |arg|
|
154
149
|
opts[:application] = arg
|
155
150
|
end
|
@@ -190,7 +185,7 @@ module RabbitFeed
|
|
190
185
|
opts[:pidfile] = arg
|
191
186
|
end
|
192
187
|
|
193
|
-
o.on '-V', '--version', 'Print version and exit' do |
|
188
|
+
o.on '-V', '--version', 'Print version and exit' do |_arg|
|
194
189
|
puts "RabbitFeed #{RabbitFeed::VERSION}"
|
195
190
|
exit 0
|
196
191
|
end
|
@@ -204,6 +199,5 @@ module RabbitFeed
|
|
204
199
|
parser.parse! argv
|
205
200
|
DEFAULTS.merge opts
|
206
201
|
end
|
207
|
-
|
208
202
|
end
|
209
203
|
end
|
@@ -7,8 +7,8 @@ module RabbitFeed
|
|
7
7
|
:consumer_exit_after_fail
|
8
8
|
validates_presence_of :application, :environment, :exchange
|
9
9
|
|
10
|
-
def initialize
|
11
|
-
RabbitFeed.log.info {{ event: :initialize_configuration, options: options.merge(
|
10
|
+
def initialize(options)
|
11
|
+
RabbitFeed.log.info { { event: :initialize_configuration, options: options.merge(password: :redacted) } }
|
12
12
|
|
13
13
|
@host = options[:host]
|
14
14
|
@hosts = options[:hosts]
|
@@ -28,10 +28,10 @@ module RabbitFeed
|
|
28
28
|
validate!
|
29
29
|
end
|
30
30
|
|
31
|
-
def self.load
|
32
|
-
RabbitFeed.log.info {{ event: :load_configuration_file, file_path: file_path, environment: environment, application: application }}
|
31
|
+
def self.load(file_path, environment, application)
|
32
|
+
RabbitFeed.log.info { { event: :load_configuration_file, file_path: file_path, environment: environment, application: application } }
|
33
33
|
|
34
|
-
raise ConfigurationError
|
34
|
+
raise ConfigurationError, "The RabbitFeed configuration file path specified does not exist: #{file_path}" unless File.exist? file_path
|
35
35
|
|
36
36
|
options = read_configuration_file file_path, environment
|
37
37
|
options[:environment] = environment
|
@@ -48,7 +48,7 @@ module RabbitFeed
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def connection_options
|
51
|
-
|
51
|
+
{}.tap do |options|
|
52
52
|
options[:heartbeat] = heartbeat if heartbeat
|
53
53
|
options[:connect_timeout] = connect_timeout if connect_timeout
|
54
54
|
options[:host] = host if host
|
@@ -65,13 +65,14 @@ module RabbitFeed
|
|
65
65
|
|
66
66
|
private
|
67
67
|
|
68
|
-
def self.read_configuration_file
|
68
|
+
def self.read_configuration_file(file_path, environment)
|
69
69
|
raw_configuration = YAML.load(ERB.new(File.read(file_path)).result)
|
70
|
-
HashWithIndifferentAccess.new
|
70
|
+
HashWithIndifferentAccess.new(raw_configuration[environment] || {})
|
71
71
|
end
|
72
|
+
private_class_method :read_configuration_file
|
72
73
|
|
73
74
|
def validate!
|
74
|
-
raise ConfigurationError
|
75
|
+
raise ConfigurationError, errors.messages if invalid?
|
75
76
|
end
|
76
77
|
end
|
77
78
|
end
|
@@ -3,10 +3,10 @@ module RabbitFeed
|
|
3
3
|
include Singleton
|
4
4
|
|
5
5
|
def initialize
|
6
|
-
RabbitFeed.log.info {{ event: :connecting_to_rabbitmq, options: RabbitFeed.configuration.connection_options.merge(
|
6
|
+
RabbitFeed.log.info { { event: :connecting_to_rabbitmq, options: RabbitFeed.configuration.connection_options.merge(password: :redacted, logger: :redacted) } }
|
7
7
|
@connection = Bunny.new RabbitFeed.configuration.connection_options
|
8
8
|
@connection.start
|
9
|
-
RabbitFeed.log.info {{ event: :connected_to_rabbitmq }}
|
9
|
+
RabbitFeed.log.info { { event: :connected_to_rabbitmq } }
|
10
10
|
@channel = @connection.create_channel
|
11
11
|
@mutex = Mutex.new
|
12
12
|
end
|
@@ -15,7 +15,7 @@ module RabbitFeed
|
|
15
15
|
|
16
16
|
attr_reader :channel, :mutex
|
17
17
|
|
18
|
-
def synchronized
|
18
|
+
def synchronized
|
19
19
|
mutex.synchronize do
|
20
20
|
yield
|
21
21
|
end
|
@@ -2,7 +2,7 @@ module RabbitFeed
|
|
2
2
|
module ConsoleConsumer
|
3
3
|
extend self
|
4
4
|
|
5
|
-
APPLICATION_NAME = 'rabbit_feed_console'
|
5
|
+
APPLICATION_NAME = 'rabbit_feed_console'.freeze
|
6
6
|
|
7
7
|
def init
|
8
8
|
@event_count = 0
|
@@ -10,10 +10,10 @@ module RabbitFeed
|
|
10
10
|
route_all_events
|
11
11
|
puts welcome_message
|
12
12
|
ask_to_purge_queue unless queue_empty?
|
13
|
-
puts
|
13
|
+
puts 'Ready. Press CTRL+C to exit.'
|
14
14
|
end
|
15
15
|
|
16
|
-
def formatted
|
16
|
+
def formatted(event)
|
17
17
|
Formatter.new(event).to_s
|
18
18
|
end
|
19
19
|
|
@@ -28,10 +28,9 @@ module RabbitFeed
|
|
28
28
|
private
|
29
29
|
|
30
30
|
def welcome_message
|
31
|
-
"
|
32
|
-
Environment: #{RabbitFeed.environment}
|
33
|
-
Queue: #{RabbitFeed.configuration.queue}
|
34
|
-
"""
|
31
|
+
"RabbitFeed console starting at #{Time.now.utc}...\n"\
|
32
|
+
"Environment: #{RabbitFeed.environment}\n"\
|
33
|
+
"Queue: #{RabbitFeed.configuration.queue}"
|
35
34
|
end
|
36
35
|
|
37
36
|
def queue_empty?
|
@@ -39,15 +38,15 @@ Queue: #{RabbitFeed.configuration.queue}
|
|
39
38
|
end
|
40
39
|
|
41
40
|
def ask_to_purge_queue
|
42
|
-
puts "There are currently #{ConsumerConnection.instance.queue_depth} message(s) in the console's queue.\n"
|
43
|
-
|
41
|
+
puts "There are currently #{ConsumerConnection.instance.queue_depth} message(s) in the console's queue.\n"\
|
42
|
+
'Would you like to purge the queue before proceeding? (y/N)>'
|
44
43
|
response = STDIN.gets.chomp
|
45
44
|
purge_queue if response == 'y'
|
46
45
|
end
|
47
46
|
|
48
47
|
def purge_queue
|
49
48
|
ConsumerConnection.instance.purge_queue
|
50
|
-
puts
|
49
|
+
puts 'Queue purged.'
|
51
50
|
end
|
52
51
|
|
53
52
|
def route_all_events
|
@@ -56,7 +55,7 @@ Queue: #{RabbitFeed.configuration.queue}
|
|
56
55
|
accept_from(:any) do
|
57
56
|
event(:any) do |event|
|
58
57
|
scope.increment_event_count
|
59
|
-
puts
|
58
|
+
puts scope.formatted(event)
|
60
59
|
puts scope.event_count_message
|
61
60
|
end
|
62
61
|
end
|
@@ -68,15 +67,14 @@ Queue: #{RabbitFeed.configuration.queue}
|
|
68
67
|
end
|
69
68
|
|
70
69
|
class Formatter
|
71
|
-
|
72
70
|
BORDER_WIDTH = 100
|
73
|
-
BORDER_CHAR =
|
74
|
-
DIVIDER_CHAR =
|
75
|
-
NEWLINE = "\n"
|
71
|
+
BORDER_CHAR = '-'.freeze
|
72
|
+
DIVIDER_CHAR = '*'.freeze
|
73
|
+
NEWLINE = "\n".freeze
|
76
74
|
|
77
75
|
attr_reader :event
|
78
76
|
|
79
|
-
def initialize
|
77
|
+
def initialize(event)
|
80
78
|
@event = event
|
81
79
|
end
|
82
80
|
|
@@ -88,12 +86,12 @@ Queue: #{RabbitFeed.configuration.queue}
|
|
88
86
|
|
89
87
|
def header
|
90
88
|
event_detail = "#{event.name}: #{event.created_at_utc}"
|
91
|
-
border_filler = BORDER_CHAR*((BORDER_WIDTH - event_detail.length)/2)
|
92
|
-
border_filler+event_detail+border_filler
|
89
|
+
border_filler = BORDER_CHAR * ((BORDER_WIDTH - event_detail.length) / 2)
|
90
|
+
border_filler + event_detail + border_filler
|
93
91
|
end
|
94
92
|
|
95
93
|
def footer
|
96
|
-
BORDER_CHAR*BORDER_WIDTH
|
94
|
+
BORDER_CHAR * BORDER_WIDTH
|
97
95
|
end
|
98
96
|
|
99
97
|
def metadata
|
@@ -101,18 +99,18 @@ Queue: #{RabbitFeed.configuration.queue}
|
|
101
99
|
end
|
102
100
|
|
103
101
|
def divider
|
104
|
-
DIVIDER_CHAR*BORDER_WIDTH
|
102
|
+
DIVIDER_CHAR * BORDER_WIDTH
|
105
103
|
end
|
106
104
|
|
107
105
|
def payload
|
108
106
|
pretty_print_hash 'Event payload', event.payload
|
109
107
|
end
|
110
108
|
|
111
|
-
def pretty_print_hash
|
109
|
+
def pretty_print_hash(description, hash)
|
112
110
|
'#' + description + NEWLINE +
|
113
|
-
|
114
|
-
|
115
|
-
|
111
|
+
hash.keys.sort.map do |key|
|
112
|
+
"#{key}: #{hash[key]}"
|
113
|
+
end.join(NEWLINE)
|
116
114
|
end
|
117
115
|
end
|
118
116
|
end
|
data/lib/rabbit_feed/consumer.rb
CHANGED
@@ -7,9 +7,9 @@ module RabbitFeed
|
|
7
7
|
def run
|
8
8
|
ConsumerConnection.instance.consume do |raw_event|
|
9
9
|
event = Event.deserialize raw_event
|
10
|
-
RabbitFeed.log.info {{ event: :message_received, metadata: event.metadata }}
|
10
|
+
RabbitFeed.log.info { { event: :message_received, metadata: event.metadata } }
|
11
11
|
event_routing.handle_event event
|
12
|
-
RabbitFeed.log.info {{ event: :message_processed, metadata: event.metadata }}
|
12
|
+
RabbitFeed.log.info { { event: :message_processed, metadata: event.metadata } }
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module RabbitFeed
|
2
2
|
class ConsumerConnection < RabbitFeed::Connection
|
3
|
-
|
4
3
|
SUBSCRIPTION_OPTIONS = {
|
5
4
|
consumer_tag: Socket.gethostname, # Use the host name of the server
|
6
5
|
manual_ack: true, # Manually acknowledge messages once they've been processed
|
@@ -15,30 +14,30 @@ module RabbitFeed
|
|
15
14
|
arguments: {
|
16
15
|
'x-ha-policy' => 'all', # Apply the queue on all mirrors
|
17
16
|
'x-expires' => SEVEN_DAYS_IN_MS, # Auto-delete the queue after a period of inactivity (in ms)
|
18
|
-
|
17
|
+
}
|
19
18
|
}.freeze
|
20
19
|
|
21
20
|
def initialize
|
22
21
|
super
|
23
22
|
channel.prefetch(1)
|
24
23
|
@queue = channel.queue RabbitFeed.configuration.queue, queue_options
|
25
|
-
RabbitFeed.log.info {{ event: :queue_declared, queue: RabbitFeed.configuration.queue, options: queue_options }}
|
24
|
+
RabbitFeed.log.info { { event: :queue_declared, queue: RabbitFeed.configuration.queue, options: queue_options } }
|
26
25
|
bind_on_accepted_routes
|
27
26
|
end
|
28
27
|
|
29
|
-
def consume
|
28
|
+
def consume(&block)
|
30
29
|
raise 'This connection already has a consumer subscribed' if connection_in_use?
|
31
30
|
synchronized do
|
32
31
|
begin
|
33
|
-
RabbitFeed.log.info {{ event: :subscribe_to_queue, queue: RabbitFeed.configuration.queue }}
|
32
|
+
RabbitFeed.log.info { { event: :subscribe_to_queue, queue: RabbitFeed.configuration.queue } }
|
34
33
|
|
35
|
-
consumer = queue.subscribe(SUBSCRIPTION_OPTIONS) do |delivery_info,
|
34
|
+
consumer = queue.subscribe(SUBSCRIPTION_OPTIONS) do |delivery_info, _properties, payload|
|
36
35
|
handle_message delivery_info, payload, &block
|
37
36
|
end
|
38
37
|
|
39
38
|
sleep # Sleep indefinitely, as the consumer runs in its own thread
|
40
|
-
rescue SystemExit,
|
41
|
-
RabbitFeed.log.info {{ event: :unsubscribe_from_queue, queue: RabbitFeed.configuration.queue }}
|
39
|
+
rescue SystemExit, SignalException
|
40
|
+
RabbitFeed.log.info { { event: :unsubscribe_from_queue, queue: RabbitFeed.configuration.queue } }
|
42
41
|
ensure
|
43
42
|
(cancel_consumer consumer) if consumer.present?
|
44
43
|
end
|
@@ -59,29 +58,29 @@ module RabbitFeed
|
|
59
58
|
|
60
59
|
def queue_options
|
61
60
|
{
|
62
|
-
auto_delete: RabbitFeed.configuration.auto_delete_queue
|
61
|
+
auto_delete: RabbitFeed.configuration.auto_delete_queue
|
63
62
|
}.merge QUEUE_OPTIONS
|
64
63
|
end
|
65
64
|
|
66
65
|
def bind_on_accepted_routes
|
67
66
|
if RabbitFeed::Consumer.event_routing.present?
|
68
67
|
RabbitFeed::Consumer.event_routing.accepted_routes.each do |accepted_route|
|
69
|
-
queue.bind(RabbitFeed.configuration.exchange,
|
70
|
-
RabbitFeed.log.info {{ event: :queue_bound, queue: RabbitFeed.configuration.queue, exchange: RabbitFeed.configuration.exchange, routing_key: accepted_route }}
|
68
|
+
queue.bind(RabbitFeed.configuration.exchange, routing_key: accepted_route)
|
69
|
+
RabbitFeed.log.info { { event: :queue_bound, queue: RabbitFeed.configuration.queue, exchange: RabbitFeed.configuration.exchange, routing_key: accepted_route } }
|
71
70
|
end
|
72
71
|
else
|
73
72
|
queue.bind(RabbitFeed.configuration.exchange)
|
74
|
-
RabbitFeed.log.info {{ event: :queue_bound, queue: RabbitFeed.configuration.queue, exchange: RabbitFeed.configuration.exchange }}
|
73
|
+
RabbitFeed.log.info { { event: :queue_bound, queue: RabbitFeed.configuration.queue, exchange: RabbitFeed.configuration.exchange } }
|
75
74
|
end
|
76
75
|
end
|
77
76
|
|
78
|
-
def acknowledge
|
77
|
+
def acknowledge(delivery_info)
|
79
78
|
queue.channel.ack(delivery_info.delivery_tag)
|
80
|
-
RabbitFeed.log.debug {{ event: :acknowledge, delivery_tag: delivery_info.delivery_tag }}
|
79
|
+
RabbitFeed.log.debug { { event: :acknowledge, delivery_tag: delivery_info.delivery_tag } }
|
81
80
|
end
|
82
81
|
|
83
|
-
def handle_message
|
84
|
-
RabbitFeed.log.debug {{ event: :handling_message, delivery_tag: delivery_info.delivery_tag }}
|
82
|
+
def handle_message(delivery_info, payload)
|
83
|
+
RabbitFeed.log.debug { { event: :handling_message, delivery_tag: delivery_info.delivery_tag } }
|
85
84
|
begin
|
86
85
|
yield payload
|
87
86
|
acknowledge delivery_info
|
@@ -91,21 +90,21 @@ module RabbitFeed
|
|
91
90
|
end
|
92
91
|
end
|
93
92
|
|
94
|
-
def cancel_consumer
|
93
|
+
def cancel_consumer(consumer)
|
95
94
|
cancel_ok = consumer.cancel
|
96
|
-
RabbitFeed.log.debug {{ event: :consumer_cancelled, status: cancel_ok, queue: RabbitFeed.configuration.queue }}
|
95
|
+
RabbitFeed.log.debug { { event: :consumer_cancelled, status: cancel_ok, queue: RabbitFeed.configuration.queue } }
|
97
96
|
end
|
98
97
|
|
99
|
-
def negative_acknowledge
|
98
|
+
def negative_acknowledge(delivery_info)
|
100
99
|
# Tell rabbit that we were unable to process the message
|
101
100
|
# This will re-queue the message
|
102
101
|
queue.channel.nack(delivery_info.delivery_tag, false, true)
|
103
|
-
RabbitFeed.log.debug {{ event: :negative_acknowledge, delivery_tag: delivery_info.delivery_tag }}
|
102
|
+
RabbitFeed.log.debug { { event: :negative_acknowledge, delivery_tag: delivery_info.delivery_tag } }
|
104
103
|
end
|
105
104
|
|
106
|
-
def handle_processing_exception
|
105
|
+
def handle_processing_exception(delivery_info, exception)
|
107
106
|
negative_acknowledge delivery_info
|
108
|
-
RabbitFeed.log.error {{ event: :processing_exception, delivery_tag: delivery_info.delivery_tag, message: exception.message, backtrace: exception.backtrace.join(',') }}
|
107
|
+
RabbitFeed.log.error { { event: :processing_exception, delivery_tag: delivery_info.delivery_tag, message: exception.message, backtrace: exception.backtrace.join(',') } }
|
109
108
|
RabbitFeed.exception_notify exception
|
110
109
|
end
|
111
110
|
end
|
data/lib/rabbit_feed/event.rb
CHANGED
@@ -2,14 +2,14 @@ module RabbitFeed
|
|
2
2
|
class Event
|
3
3
|
include ActiveModel::Validations
|
4
4
|
|
5
|
-
SCHEMA_VERSION = '2.0.0'
|
5
|
+
SCHEMA_VERSION = '2.0.0'.freeze
|
6
6
|
|
7
7
|
attr_reader :schema, :payload, :metadata, :sensitive_fields
|
8
8
|
validates :metadata, presence: true
|
9
9
|
validates :payload, length: { minimum: 0, allow_nil: false, message: 'can\'t be nil' }
|
10
10
|
validate :required_metadata
|
11
11
|
|
12
|
-
def initialize
|
12
|
+
def initialize(metadata, payload = {}, schema = nil, sensitive_fields = [])
|
13
13
|
@schema = schema
|
14
14
|
@payload = payload.with_indifferent_access if payload
|
15
15
|
@metadata = metadata.with_indifferent_access if metadata
|
@@ -40,8 +40,7 @@ module RabbitFeed
|
|
40
40
|
end
|
41
41
|
|
42
42
|
class << self
|
43
|
-
|
44
|
-
def deserialize serialized_event
|
43
|
+
def deserialize(serialized_event)
|
45
44
|
datum_reader = Avro::IO::DatumReader.new
|
46
45
|
reader = Avro::DataFile::Reader.new (StringIO.new serialized_event), datum_reader
|
47
46
|
event_hash = nil
|
@@ -49,25 +48,7 @@ module RabbitFeed
|
|
49
48
|
event_hash = datum
|
50
49
|
end
|
51
50
|
reader.close
|
52
|
-
|
53
|
-
new_from_version_1 event_hash, datum_reader.readers_schema
|
54
|
-
else
|
55
|
-
new event_hash['metadata'], event_hash['payload'], datum_reader.readers_schema
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
def version_1? event_hash
|
62
|
-
%w(metadata payload).none?{|key| event_hash.has_key? key}
|
63
|
-
end
|
64
|
-
|
65
|
-
def new_from_version_1 metadata_and_payload, schema
|
66
|
-
metadata = {}
|
67
|
-
%w(application name host version environment created_at_utc).each do |field|
|
68
|
-
metadata[field] = metadata_and_payload.delete field
|
69
|
-
end
|
70
|
-
new metadata, metadata_and_payload, schema
|
51
|
+
new event_hash['metadata'], event_hash['payload'], datum_reader.readers_schema
|
71
52
|
end
|
72
53
|
end
|
73
54
|
|
@@ -75,18 +56,17 @@ module RabbitFeed
|
|
75
56
|
|
76
57
|
def sensitive_proof_payload
|
77
58
|
sensitive_fields.each_with_object(payload.dup) do |field, clean_payload|
|
78
|
-
clean_payload[field] =
|
59
|
+
clean_payload[field] = '[REMOVED]' if clean_payload.key?(field)
|
79
60
|
end
|
80
61
|
end
|
81
62
|
|
82
63
|
def validate!
|
83
|
-
raise Error
|
64
|
+
raise Error, "Invalid event: #{errors.messages}" if invalid?
|
84
65
|
end
|
85
66
|
|
86
67
|
def required_metadata
|
87
|
-
|
88
|
-
|
89
|
-
end
|
68
|
+
return unless metadata
|
69
|
+
errors.add(:metadata, 'name field is required') if metadata[:name].blank?
|
90
70
|
end
|
91
71
|
end
|
92
72
|
end
|
@@ -1,13 +1,12 @@
|
|
1
1
|
module RabbitFeed
|
2
2
|
class EventDefinitions
|
3
|
-
|
4
3
|
class Field
|
5
4
|
include ActiveModel::Validations
|
6
5
|
|
7
6
|
attr_reader :name, :type, :definition
|
8
7
|
validates_presence_of :name, :type, :definition
|
9
8
|
|
10
|
-
def initialize
|
9
|
+
def initialize(name, type, definition)
|
11
10
|
@name = name
|
12
11
|
@type = type
|
13
12
|
@definition = definition
|
@@ -21,7 +20,7 @@ module RabbitFeed
|
|
21
20
|
private
|
22
21
|
|
23
22
|
def validate!
|
24
|
-
raise ConfigurationError
|
23
|
+
raise ConfigurationError, "Bad field specification for #{name}: #{errors.messages}" if invalid?
|
25
24
|
end
|
26
25
|
end
|
27
26
|
|
@@ -33,24 +32,24 @@ module RabbitFeed
|
|
33
32
|
validate :schema_parseable
|
34
33
|
validates :version, format: { with: /\A\d+\.\d+\.\d+\z/, message: 'must be in *.*.* format' }
|
35
34
|
|
36
|
-
def initialize
|
35
|
+
def initialize(name, version)
|
37
36
|
@name = name
|
38
37
|
@version = version
|
39
38
|
@fields = []
|
40
39
|
@sensitive_fields = []
|
41
40
|
end
|
42
41
|
|
43
|
-
def payload_contains
|
44
|
-
|
42
|
+
def payload_contains(&block)
|
43
|
+
instance_eval(&block)
|
45
44
|
end
|
46
45
|
|
47
|
-
def field
|
46
|
+
def field(name, options)
|
48
47
|
sensitive_fields << name.to_s if options.delete(:sensitive)
|
49
48
|
fields << (Field.new name, options[:type], options[:definition])
|
50
49
|
end
|
51
50
|
|
52
|
-
def defined_as
|
53
|
-
@definition =
|
51
|
+
def defined_as(&block)
|
52
|
+
@definition = yield if block.present?
|
54
53
|
end
|
55
54
|
|
56
55
|
def payload_schema
|
@@ -65,23 +64,23 @@ module RabbitFeed
|
|
65
64
|
(Field.new 'version', 'string', 'The version of the event payload'),
|
66
65
|
(Field.new 'schema_version', 'string', 'The version of the event schema'),
|
67
66
|
(Field.new 'name', 'string', 'The name of the event'),
|
68
|
-
(Field.new 'created_at_utc', 'string', 'The UTC time that the event was created')
|
67
|
+
(Field.new 'created_at_utc', 'string', 'The UTC time that the event was created')
|
69
68
|
].map(&:schema) }
|
70
69
|
end
|
71
70
|
|
72
71
|
def event_schema
|
73
72
|
[
|
74
73
|
{ name: 'payload', type: payload_schema, doc: 'The event payload (defined by the source system)' },
|
75
|
-
{ name: 'metadata', type: metadata_schema, doc: 'The event metadata (defined by rabbit feed)' }
|
74
|
+
{ name: 'metadata', type: metadata_schema, doc: 'The event metadata (defined by rabbit feed)' }
|
76
75
|
]
|
77
76
|
end
|
78
77
|
|
79
78
|
def schema
|
80
|
-
@schema ||=
|
79
|
+
@schema ||= Avro::Schema.parse({ name: name, type: 'record', doc: definition, fields: event_schema }.to_json)
|
81
80
|
end
|
82
81
|
|
83
82
|
def validate!
|
84
|
-
raise ConfigurationError
|
83
|
+
raise ConfigurationError, "Bad event specification for #{name}: #{errors.messages}" if invalid?
|
85
84
|
end
|
86
85
|
|
87
86
|
private
|
@@ -99,13 +98,13 @@ module RabbitFeed
|
|
99
98
|
@events = {}
|
100
99
|
end
|
101
100
|
|
102
|
-
def define_event
|
101
|
+
def define_event(name, options, &block)
|
103
102
|
events[name] = Event.new name, options[:version]
|
104
103
|
events[name].instance_eval(&block)
|
105
104
|
events[name].validate!
|
106
105
|
end
|
107
106
|
|
108
|
-
def []
|
107
|
+
def [](name)
|
109
108
|
events[name]
|
110
109
|
end
|
111
110
|
end
|