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 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