banter 0.5.0 → 0.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9d0f8fa6fb6efcc9befadeaac184560b989606f3
4
- data.tar.gz: 55445f64eb387bacb905718ba61284d272d72db7
3
+ metadata.gz: aaa823c8ce67b181474196407a8f92bab5c396ab
4
+ data.tar.gz: 0a3a05c26ce018f3b4c19a9c849d2cb353c26991
5
5
  SHA512:
6
- metadata.gz: 820d17a44fdb4b450f0d982c689ccc16883d589208c990db1b9e7279109876e55585c11404dc5dccf1839e823fe513ef59575c37d010784ec6f3f529e2195b6a
7
- data.tar.gz: 5e74cca350d83ac11c6a8e32ca4fb771d931593616839b5086f4f25118a48d5e3a6f663dd7b3aea6833a7c3910b029687bca1c75afe8eb6850a9da57f7f1a209
6
+ metadata.gz: 78049fb9a4a98e41e8fe0e8f09bf40ec192529ea0fa79d76a84d9664ac9c529fc1f30e281cb7b464f732ce084f39d399b4c1eb1a2d9586a0a44878ef73d98724
7
+ data.tar.gz: 961168e99870c9cde24af76a2cea4182eacff90235d990ba046d61cbd561da4df8205a80558aec08f85f3110601cfaecfa8617dc465f4fb3509493cdda0def15
@@ -3,3 +3,9 @@ rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
5
  - 2.1.0
6
+ services:
7
+ - mongodb
8
+ before_script:
9
+ - sleep 5
10
+ - mongo mydb_test --eval 'db.addUser("travis", "test");'
11
+
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', { id: 3, name: 'Foo Bar', email: 'foobar@email.com')
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
@@ -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
@@ -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 '-v', '--version', "Print version and exit" do |arg|
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.new(subscriber_classes).start
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
@@ -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) )
@@ -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
- @app.call(env)
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
@@ -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, enabled = true)
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? || !enabled
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
@@ -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
- Banter.logger.tagged(tags) { Banter.logger.debug message[:payload].as_json }
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
- Banter.logger.tagged(tags) { Banter.logger.error message[:payload].as_json }
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
- Banter.logger.tagged(tags) { Banter.logger.debug message[:payload].as_json }
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
- Banter.logger.tagged(tags) {Banter.logger.info message[:payload].as_json}
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
@@ -11,7 +11,12 @@ module Banter
11
11
  end
12
12
 
13
13
  config.after_initialize do
14
- Banter.logger = Rails.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.logger.debug("Message received by listener on #{worker_class.name} with payload: #{envelope[:payload]}")
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
- worker.perform!(envelope[:payload])
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
- Banter.logger.error("Failed message for #{worker_class.name}")
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
- def teardown
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.logger.debug("Message delivery with contents: #{contents}")
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