banter 0.5.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -0
- data/Gemfile +9 -0
- data/README.md +37 -3
- data/lib/banter.rb +4 -0
- data/lib/banter/cli.rb +13 -3
- data/lib/banter/configuration.rb +10 -0
- data/lib/banter/middleware.rb +9 -1
- data/lib/banter/publisher.rb +45 -4
- data/lib/banter/rabbit_logger.rb +22 -4
- data/lib/banter/railtie.rb +6 -1
- data/lib/banter/server/client_queue_listener.rb +31 -7
- data/lib/banter/server/rabbit_mq_subscriber.rb +2 -1
- data/lib/banter/server/subscriber_server.rb +55 -23
- data/lib/banter/subscriber.rb +0 -1
- data/lib/banter/version.rb +1 -1
- data/lib/banter/web.rb +28 -0
- data/lib/banter/web/application.rb +36 -0
- data/lib/banter/web/helpers.rb +9 -0
- data/lib/banter/web/models/banter_message.rb +82 -0
- data/lib/banter/web/models/banter_worker.rb +68 -0
- data/spec/banter/cli_spec.rb +28 -6
- data/spec/banter/publisher_spec.rb +78 -0
- data/spec/banter/rabbit_logger_spec.rb +27 -2
- data/spec/banter/server/client_queue_listener_spec.rb +86 -22
- data/spec/banter/server/subscriber_server_spec.rb +87 -0
- data/spec/banter/web/application_spec.rb +10 -0
- data/spec/factories/banter_workers.rb +13 -0
- data/spec/mongoid.yml +7 -0
- data/spec/spec_helper.rb +28 -9
- data/web/assets/stylesheets/banter.css +66 -0
- data/web/views/index.haml +32 -0
- data/web/views/layout.haml +45 -0
- data/web/views/message.haml +52 -0
- data/web/views/messages.haml +20 -0
- data/web/views/subscribers.haml +17 -0
- data/web/views/worker.haml +51 -0
- metadata +24 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aaa823c8ce67b181474196407a8f92bab5c396ab
|
4
|
+
data.tar.gz: 0a3a05c26ce018f3b4c19a9c849d2cb353c26991
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78049fb9a4a98e41e8fe0e8f09bf40ec192529ea0fa79d76a84d9664ac9c529fc1f30e281cb7b464f732ce084f39d399b4c1eb1a2d9586a0a44878ef73d98724
|
7
|
+
data.tar.gz: 961168e99870c9cde24af76a2cea4182eacff90235d990ba046d61cbd561da4df8205a80558aec08f85f3110601cfaecfa8617dc465f4fb3509493cdda0def15
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -2,3 +2,12 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in banter.gemspec
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
group :test do
|
7
|
+
gem 'rack-test'
|
8
|
+
gem 'sinatra'
|
9
|
+
gem 'haml'
|
10
|
+
gem 'mongoid', github: 'mongoid/mongoid'
|
11
|
+
gem 'timecop'
|
12
|
+
gem "factory_girl", "~> 4.0"
|
13
|
+
end
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Banter
|
1
|
+
# Banter
|
2
2
|
|
3
3
|
Simple Publishers and subscribers for Ruby using RabbitMQ
|
4
4
|
|
@@ -33,7 +33,7 @@ There are two sides to this gem, publishers and subscribers
|
|
33
33
|
|
34
34
|
Publishing to a message is super simple
|
35
35
|
|
36
|
-
Banter.publish('user.created',
|
36
|
+
Banter.publish('user.created', id: 3, name: 'Foo Bar', email: 'foobar@email.com')
|
37
37
|
|
38
38
|
Additionally you can setup a context that is passed down to the subscribers. This context can be set up in a `before_filter`
|
39
39
|
|
@@ -81,11 +81,12 @@ Usage: bundle exec start_subscribers [options]
|
|
81
81
|
-P, --pidfile PATH path to pidfile
|
82
82
|
-o, --only [SUBSCRIBERS] comma separated name of subsriber classes that should be run
|
83
83
|
-r, --require [PATH|DIR] Location of Rails application with workers or file to require
|
84
|
+
-n, --name [PROCESS_NAME] Name of the process
|
84
85
|
-v, --version Print version and exit
|
85
86
|
|
86
87
|
```
|
87
88
|
|
88
|
-
#### Validations
|
89
|
+
#### Payload Validations
|
89
90
|
|
90
91
|
```ruby
|
91
92
|
class UserWelcomeSubscriber < Banter::Subscriber
|
@@ -107,6 +108,39 @@ class UserWelcomeSubscriber < Banter::Subscriber
|
|
107
108
|
end
|
108
109
|
```
|
109
110
|
|
111
|
+
### Dashboards!!!
|
112
|
+
|
113
|
+
Banter provides dashboards that can be easily enabled in any Rack application.
|
114
|
+
|
115
|
+
For a Rails application
|
116
|
+
|
117
|
+
```
|
118
|
+
# config/application.rb
|
119
|
+
config.banter.web_enabled = true
|
120
|
+
```
|
121
|
+
|
122
|
+
```
|
123
|
+
#config/routes.rb
|
124
|
+
mount Banter::Web::Application => '/banter'
|
125
|
+
```
|
126
|
+
|
127
|
+
or
|
128
|
+
|
129
|
+
```
|
130
|
+
#config.routes.rb
|
131
|
+
authenticate :admin do
|
132
|
+
mount Banter::Web::Application => '/banter'
|
133
|
+
end
|
134
|
+
```
|
135
|
+
|
136
|
+
Dashboards currently use Mongo DB (Mongoid gem) to store data, so the following gems should be added to your gemfile
|
137
|
+
```
|
138
|
+
gem 'mongoid'
|
139
|
+
gem 'haml'
|
140
|
+
gem 'sinatra'
|
141
|
+
```
|
142
|
+
|
143
|
+
|
110
144
|
## Contributing
|
111
145
|
|
112
146
|
1. Fork it
|
data/lib/banter.rb
CHANGED
@@ -31,6 +31,10 @@ module Banter
|
|
31
31
|
Publisher.instance.publish(Banter::Context.instance, routing_key, payload)
|
32
32
|
end
|
33
33
|
|
34
|
+
def self.delay_messages
|
35
|
+
Publisher.instance.delay_messages{ yield }
|
36
|
+
end
|
37
|
+
|
34
38
|
# @return [Logger] Logger used for logging through the Banter gem
|
35
39
|
def self.logger
|
36
40
|
return Banter::Configuration.logger if Banter::Configuration.logger
|
data/lib/banter/cli.rb
CHANGED
@@ -10,7 +10,7 @@ module Banter
|
|
10
10
|
class CLI
|
11
11
|
include Singleton
|
12
12
|
|
13
|
-
attr_accessor :pidfile, :subscribers
|
13
|
+
attr_accessor :pidfile, :subscribers, :process_name
|
14
14
|
|
15
15
|
# Method to support parsing of arguments passed through the command line
|
16
16
|
def parse(args = ARGV)
|
@@ -28,7 +28,11 @@ module Banter
|
|
28
28
|
@require_path = arg
|
29
29
|
end
|
30
30
|
|
31
|
-
opts.on '-
|
31
|
+
opts.on '-n', '--name [PROCESS_NAME]', "Name of the process" do |arg|
|
32
|
+
@process_name = arg
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on '-v', '--version', "Print version and exit" do
|
32
36
|
puts "Banter #{Banter::VERSION}"
|
33
37
|
abort
|
34
38
|
end
|
@@ -38,6 +42,7 @@ module Banter
|
|
38
42
|
end
|
39
43
|
|
40
44
|
def run
|
45
|
+
change_process_name
|
41
46
|
load_environment
|
42
47
|
write_pidfile
|
43
48
|
load_subscribers
|
@@ -87,7 +92,12 @@ module Banter
|
|
87
92
|
end
|
88
93
|
|
89
94
|
def load_subscribers
|
90
|
-
Banter::Server::SubscriberServer.
|
95
|
+
Banter::Server::SubscriberServer.instance.set_workers(subscriber_classes)
|
96
|
+
Banter::Server::SubscriberServer.instance.start
|
97
|
+
end
|
98
|
+
|
99
|
+
def change_process_name
|
100
|
+
$PROGRAM_NAME = @process_name || "banter_subscriber"
|
91
101
|
end
|
92
102
|
end
|
93
103
|
end
|
data/lib/banter/configuration.rb
CHANGED
@@ -30,10 +30,20 @@ module Banter
|
|
30
30
|
mattr_accessor :push_enabled
|
31
31
|
@@push_enabled = true
|
32
32
|
|
33
|
+
# RabbitMQ batches messages and delays until the end of a request
|
34
|
+
# @default false
|
35
|
+
mattr_accessor :batch_messages
|
36
|
+
@@batch_messages = false
|
37
|
+
|
33
38
|
# Dead letter queue name
|
34
39
|
# @default nil
|
35
40
|
mattr_accessor :dead_letter_queue
|
36
41
|
|
42
|
+
# Enable web monitoring
|
43
|
+
# @default false
|
44
|
+
mattr_accessor :web_enabled
|
45
|
+
@@web_enabled = false
|
46
|
+
|
37
47
|
def self.configure_with(environment_name, yaml_file = nil)
|
38
48
|
@@yaml_file = yaml_file.nil? ? "config/banter.yml" : yaml_file
|
39
49
|
@@all_conf = Hashie::Mash.new(YAML.load_file(@@yaml_file) )
|
data/lib/banter/middleware.rb
CHANGED
@@ -2,13 +2,21 @@
|
|
2
2
|
|
3
3
|
module Banter
|
4
4
|
class Middleware
|
5
|
+
@@cached = nil
|
5
6
|
def initialize(app)
|
6
7
|
@app = app
|
7
8
|
end
|
8
9
|
|
9
10
|
def call(env)
|
10
11
|
Banter::Context.clear!
|
11
|
-
|
12
|
+
if @@cached.nil?
|
13
|
+
@@cached = Banter::Configuration.batch_messages
|
14
|
+
end
|
15
|
+
if @@cached
|
16
|
+
Banter.dedupe_messages { @app.call(env) }
|
17
|
+
else
|
18
|
+
@app.call(env)
|
19
|
+
end
|
12
20
|
end
|
13
21
|
end
|
14
22
|
end
|
data/lib/banter/publisher.rb
CHANGED
@@ -15,6 +15,7 @@ module Banter
|
|
15
15
|
def initialize(exchange = nil)
|
16
16
|
@exchange = exchange || Banter::Configuration.exchange_name
|
17
17
|
@disabled = false
|
18
|
+
@batch_messages = false
|
18
19
|
end
|
19
20
|
|
20
21
|
def enable(value)
|
@@ -22,6 +23,13 @@ module Banter
|
|
22
23
|
@disabled
|
23
24
|
end
|
24
25
|
|
26
|
+
def delay_messages
|
27
|
+
delay_start
|
28
|
+
yield
|
29
|
+
ensure
|
30
|
+
delay_execute
|
31
|
+
end
|
32
|
+
|
25
33
|
def start
|
26
34
|
unless Configuration.push_enabled
|
27
35
|
@disabled = true
|
@@ -41,7 +49,6 @@ module Banter
|
|
41
49
|
return
|
42
50
|
end
|
43
51
|
|
44
|
-
|
45
52
|
@publisher.on_return do |return_info, properties, content|
|
46
53
|
# contents are already transformed into message that we want to send
|
47
54
|
Banter::RabbitLogger.failed_publish(return_info[:routing_key], properties, content)
|
@@ -49,29 +56,45 @@ module Banter
|
|
49
56
|
|
50
57
|
end
|
51
58
|
|
52
|
-
def publish(context, key, payload
|
59
|
+
def publish(context, key, payload)
|
53
60
|
routing_key = "#{@exchange}.#{key}"
|
54
61
|
envelope = ::Banter::Message.new.serialize(context, key, payload)
|
55
62
|
|
63
|
+
if @batch_messages
|
64
|
+
add_message(routing_key, envelope)
|
65
|
+
else
|
66
|
+
execute_publish(routing_key, envelope)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
def execute_publish(routing_key, envelope)
|
72
|
+
|
56
73
|
if @publisher.nil?
|
57
74
|
start
|
58
75
|
end
|
59
76
|
|
60
|
-
if @disabled || @publisher.nil?
|
77
|
+
if @disabled || @publisher.nil?
|
61
78
|
Banter::RabbitLogger.failed_publish(routing_key, {}, envelope)
|
62
79
|
else
|
63
80
|
tries = 2
|
64
81
|
begin
|
65
82
|
@publisher.publish(envelope.to_json, :persistent => true, :mandatory => true, :timestamp => envelope[:ts], :content_type => "application/json", :routing_key => routing_key)
|
66
83
|
Banter::RabbitLogger.log_publish(routing_key, envelope)
|
84
|
+
# FIX!!! -thl
|
85
|
+
# What kind of errors could be fired from a failure to publish?
|
86
|
+
# Should we be more specific?
|
87
|
+
# Docs only have errors while connecting, and really not during some sort of long running socket. For now
|
88
|
+
# We'll log until we get more info.
|
67
89
|
rescue => e
|
90
|
+
Banter::RabbitLogger.log(Logger::WARN, "Error occured on publish: #{e.message}: #{e.inspect}, #{routing_key}: #{envelope.inspect}")
|
68
91
|
tries -= 1
|
69
92
|
teardown
|
70
93
|
start
|
71
94
|
if tries > 0 && @publisher
|
72
95
|
retry
|
73
96
|
else
|
74
|
-
Banter::RabbitLogger.failed_publish(routing_key, {}, envelope)
|
97
|
+
Banter::RabbitLogger.failed_publish(routing_key, { error: e.message }, envelope)
|
75
98
|
end
|
76
99
|
end
|
77
100
|
|
@@ -80,9 +103,27 @@ module Banter
|
|
80
103
|
end
|
81
104
|
|
82
105
|
def teardown
|
106
|
+
# FIX!!! -thl
|
107
|
+
# How can I check to see if the connection is valid quickly without trying a close on the connection?
|
83
108
|
@connection.close
|
84
109
|
@publisher = nil
|
85
110
|
end
|
86
111
|
|
112
|
+
def delay_start
|
113
|
+
@batch_messages = true
|
114
|
+
@messages = []
|
115
|
+
end
|
116
|
+
|
117
|
+
def delay_execute
|
118
|
+
@messages.each do |key, envelope|
|
119
|
+
execute_publish(key, envelope)
|
120
|
+
end
|
121
|
+
@messages.clear
|
122
|
+
@batch_messages = false
|
123
|
+
end
|
124
|
+
|
125
|
+
def add_message(key, envelope)
|
126
|
+
@messages << [key, envelope]
|
127
|
+
end
|
87
128
|
end
|
88
129
|
end
|
data/lib/banter/rabbit_logger.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# Internal log class that is used to log messages before sending, after receiving, and failure to send
|
2
2
|
module Banter
|
3
|
+
|
3
4
|
class RabbitLogger
|
4
5
|
def self.enabled?
|
5
6
|
Banter::Configuration.logging_enabled
|
@@ -8,25 +9,42 @@ module Banter
|
|
8
9
|
def self.log_publish(routing_key, message)
|
9
10
|
return unless enabled?
|
10
11
|
tags = ["BANTER PUBLISH", message[:ts], message[:pub], message[:v], routing_key]
|
11
|
-
|
12
|
+
logger.tagged(tags) { logger.warn message[:payload].as_json }
|
12
13
|
end
|
13
14
|
|
14
15
|
def self.failed_publish(routing_key, properties, message)
|
15
16
|
return unless enabled?
|
16
17
|
tags = ["BANTER FAILED_SEND", message[:ts], message[:pub], message[:v], routing_key]
|
17
|
-
|
18
|
+
logger.tagged(tags) { logger.error( { properties: properties, payload:message[:payload] }.as_json ) }
|
18
19
|
end
|
19
20
|
|
20
21
|
def self.log_receive(routing_key, message)
|
21
22
|
return unless enabled?
|
22
23
|
tags = ["BANTER RECEIVED", message[:ts], message[:pub], message[:v], routing_key, Process::pid]
|
23
|
-
|
24
|
+
logger.tagged(tags) { logger.warn message[:payload].as_json }
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.log_complete(routing_key, message)
|
28
|
+
return unless enabled?
|
29
|
+
tags = ["BANTER COMPLETED", message[:ts], message[:pub], message[:v], routing_key, Process::pid]
|
30
|
+
logger.tagged(tags) { logger.warn message[:payload].as_json }
|
24
31
|
end
|
25
32
|
|
26
33
|
def self.log_service(service_name, message)
|
27
34
|
return unless enabled?
|
28
35
|
tags = ["BANTER SERVICE", service_name, Process::pid]
|
29
|
-
|
36
|
+
logger.tagged(tags) { logger.info message.as_json}
|
30
37
|
end
|
38
|
+
|
39
|
+
@@log_map = [:debug, :info, :warn, :error, :fatal]
|
40
|
+
def self.log(log_level, message)
|
41
|
+
tags = ["BANTER LOG_LEVEL:#{@@log_map[log_level].capitalize.to_s}", Process::pid]
|
42
|
+
logger.tagged(tags) { logger.send(@@log_map[log_level], message.as_json ) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.logger
|
46
|
+
Banter.logger
|
47
|
+
end
|
48
|
+
|
31
49
|
end
|
32
50
|
end
|
data/lib/banter/railtie.rb
CHANGED
@@ -11,7 +11,12 @@ module Banter
|
|
11
11
|
end
|
12
12
|
|
13
13
|
config.after_initialize do
|
14
|
-
Banter.logger =
|
14
|
+
Banter.logger = config.banter.logger if config.banter.logger
|
15
|
+
|
16
|
+
if Banter::Configuration.web_enabled
|
17
|
+
require 'banter/web'
|
18
|
+
end
|
19
|
+
|
15
20
|
end
|
16
21
|
|
17
22
|
console do
|
@@ -28,17 +28,27 @@ module Banter
|
|
28
28
|
|
29
29
|
def shutdown
|
30
30
|
@subscriber.teardown if @subscriber.present?
|
31
|
-
# TODO -thl
|
32
|
-
# This the kosher thing to do in ruby?
|
33
|
-
teardown
|
34
31
|
end
|
35
32
|
|
36
33
|
def message_received(delivery_info, properties, envelope)
|
37
|
-
Banter.
|
34
|
+
Banter::RabbitLogger.log(::Logger::DEBUG, "Message received by listener on #{worker_class.name} with payload: #{envelope[:payload]}")
|
38
35
|
worker = worker_class.new(delivery_info, properties, envelope[:context])
|
39
|
-
|
36
|
+
payload = envelope[:payload]
|
37
|
+
track_message_received(worker, payload)
|
38
|
+
worker.perform!(payload)
|
39
|
+
track_message_successful
|
40
|
+
rescue ::Banter::PayloadValidationError => banter_error
|
41
|
+
track_message_failed(banter_error.message, false)
|
42
|
+
Banter::RabbitLogger.log(::Logger::ERROR, { worker: worker_class.name, message: banter_error.message, envelope: envelope })
|
43
|
+
Airbrake.notify("Validation error for #{worker_class.name}",
|
44
|
+
params: {
|
45
|
+
info: delivery_info,
|
46
|
+
properties: properties,
|
47
|
+
context: envelope[:context],
|
48
|
+
payload: envelope[:payload] })
|
40
49
|
rescue => e
|
41
|
-
|
50
|
+
track_message_failed(e.message, true)
|
51
|
+
Banter::RabbitLogger.log(::Logger::DEBUG, "Failed message for #{worker_class.name} #{e.message}")
|
42
52
|
Airbrake.notify("Failed perform for #{worker_class.name}",
|
43
53
|
params: {
|
44
54
|
info: delivery_info,
|
@@ -49,7 +59,21 @@ module Banter
|
|
49
59
|
end
|
50
60
|
|
51
61
|
protected
|
52
|
-
|
62
|
+
|
63
|
+
def track_message_successful
|
64
|
+
return unless Banter::Configuration.web_enabled
|
65
|
+
BanterMessage.message_successful
|
66
|
+
end
|
67
|
+
|
68
|
+
def track_message_failed(error_message, error = true)
|
69
|
+
return unless Banter::Configuration.web_enabled
|
70
|
+
status = error ? BanterMessage::STATUS_ERROR : BanterMessage::STATUS_VALIDATION_FAILED
|
71
|
+
BanterMessage.message_failed(error_message, status)
|
72
|
+
end
|
73
|
+
|
74
|
+
def track_message_received(worker, payload)
|
75
|
+
return unless Banter::Configuration.web_enabled
|
76
|
+
BanterMessage.message_received(worker_class, worker, payload)
|
53
77
|
end
|
54
78
|
end
|
55
79
|
end
|
@@ -54,13 +54,14 @@ module Banter
|
|
54
54
|
@consumer = @listener.subscribe(consumer_tag: name, block: blocking)
|
55
55
|
|
56
56
|
@consumer.on_delivery do |delivery_info, properties, contents|
|
57
|
-
Banter.
|
57
|
+
Banter::RabbitLogger.log(Logger::DEBUG, "Message delivery with contents: #{contents}")
|
58
58
|
if delivery_info[:redelivered]
|
59
59
|
Airbrake.notify("PubSub Message redelivery", params: { info: delivery_info, props: properties, contents: contents }, environment_name: ENV['RAILS_ENV'])
|
60
60
|
end
|
61
61
|
message = ::Banter::Message.new.parse(contents)
|
62
62
|
Banter::RabbitLogger.log_receive(delivery_info[:routing_key], message)
|
63
63
|
yield delivery_info, properties, message
|
64
|
+
Banter::RabbitLogger.log_complete(delivery_info[:routing_key], message)
|
64
65
|
true
|
65
66
|
end
|
66
67
|
end
|