rabbitek 0.1.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 48e1e5b2bc2ea1d5a8de3e5e6e35ec5f8ef3e6961a99987f544f87dfc043d48f
4
+ data.tar.gz: b413bb3111ab56d0cf135bc5af3c6a754c675d945cf5919932881e57f31c6d94
5
+ SHA512:
6
+ metadata.gz: 04dc3c2f72c7157d9d7eb987aa91c9e97fcbd588c2a06079e37db8fba3a5d2ef202cb52337cc9b7065d4ec711e1976a132efbd53dce5fba8f01a037b1afe9585
7
+ data.tar.gz: c5b4d5946c8ebf3a8f374de46ec73ce40504d32a0fed75329fc568e027b12697d19c2c2dab1562da03a9ea60abce242c9ebe907c96a8f66b5f9963fbcff0aced
data/.codeclimate.yml ADDED
@@ -0,0 +1,4 @@
1
+ plugins:
2
+ rubocop:
3
+ enabled: true
4
+ channel: rubocop-0-58
data/.gitignore ADDED
@@ -0,0 +1,58 @@
1
+ # Created by https://www.gitignore.io/api/ruby
2
+
3
+ ### Ruby ###
4
+ *.gem
5
+ *.rbc
6
+ /.config
7
+ /coverage/
8
+ /InstalledFiles
9
+ /pkg/
10
+ /spec/reports/
11
+ /spec/examples.txt
12
+ /test/tmp/
13
+ /test/version_tmp/
14
+ /tmp/
15
+
16
+ # Used by dotenv library to load environment variables.
17
+ # .env
18
+
19
+ ## Specific to RubyMotion:
20
+ .dat*
21
+ .repl_history
22
+ build/
23
+ *.bridgesupport
24
+ build-iPhoneOS/
25
+ build-iPhoneSimulator/
26
+
27
+ ## Specific to RubyMotion (use of CocoaPods):
28
+ #
29
+ # We recommend against adding the Pods directory to your .gitignore. However
30
+ # you should judge for yourself, the pros and cons are mentioned at:
31
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
32
+ #
33
+ # vendor/Pods/
34
+
35
+ ## Documentation cache and generated files:
36
+ /.yardoc/
37
+ /_yardoc/
38
+ /doc/
39
+ /rdoc/
40
+
41
+ ## Environment normalization:
42
+ /.bundle/
43
+ /vendor/bundle
44
+ /lib/bundler/man/
45
+
46
+ # for a library or gem, you might want to ignore these files since the code is
47
+ # intended to run in multiple environments; otherwise, check them in:
48
+ # Gemfile.lock
49
+ # .ruby-version
50
+ # .ruby-gemset
51
+
52
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
53
+ .rvmrc
54
+
55
+
56
+ # End of https://www.gitignore.io/api/ruby
57
+
58
+ .idea
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,19 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'vendor/**/*'
4
+ - 'spec/fixtures/**/*'
5
+ - 'tmp/**/*'
6
+ TargetRubyVersion: 2.4
7
+
8
+ Metrics/LineLength:
9
+ Max: 120
10
+
11
+ Metrics/BlockLength:
12
+ Exclude:
13
+ - 'rabbitek.gemspec'
14
+ - 'Rakefile'
15
+ - '**/*.rake'
16
+ - 'spec/**/*.rb'
17
+
18
+ Metrics/MethodLength:
19
+ Max: 15
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.0
5
+ - 2.4.0
6
+ - 2.5.1
7
+ before_install: gem install bundler -N
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ # Specify your gem's dependencies in rabbitek.gemspec
8
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,76 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rabbitek (0.1.0)
5
+ activesupport (> 3.0)
6
+ bunny (~> 2.11.0)
7
+ oj (~> 3.6)
8
+ opentracing (~> 0.4)
9
+ slop (~> 4.0)
10
+
11
+ GEM
12
+ remote: https://rubygems.org/
13
+ specs:
14
+ activesupport (5.2.1)
15
+ concurrent-ruby (~> 1.0, >= 1.0.2)
16
+ i18n (>= 0.7, < 2)
17
+ minitest (~> 5.1)
18
+ tzinfo (~> 1.1)
19
+ amq-protocol (2.3.0)
20
+ ast (2.4.0)
21
+ bunny (2.11.0)
22
+ amq-protocol (~> 2.3.0)
23
+ concurrent-ruby (1.0.5)
24
+ diff-lcs (1.3)
25
+ i18n (1.1.1)
26
+ concurrent-ruby (~> 1.0)
27
+ jaro_winkler (1.5.1)
28
+ minitest (5.11.3)
29
+ oj (3.7.0)
30
+ opentracing (0.4.3)
31
+ parallel (1.12.1)
32
+ parser (2.5.1.2)
33
+ ast (~> 2.4.0)
34
+ powerpack (0.1.2)
35
+ rainbow (3.0.0)
36
+ rake (10.5.0)
37
+ rspec (3.7.0)
38
+ rspec-core (~> 3.7.0)
39
+ rspec-expectations (~> 3.7.0)
40
+ rspec-mocks (~> 3.7.0)
41
+ rspec-core (3.7.1)
42
+ rspec-support (~> 3.7.0)
43
+ rspec-expectations (3.7.0)
44
+ diff-lcs (>= 1.2.0, < 2.0)
45
+ rspec-support (~> 3.7.0)
46
+ rspec-mocks (3.7.0)
47
+ diff-lcs (>= 1.2.0, < 2.0)
48
+ rspec-support (~> 3.7.0)
49
+ rspec-support (3.7.1)
50
+ rubocop (0.58.2)
51
+ jaro_winkler (~> 1.5.1)
52
+ parallel (~> 1.10)
53
+ parser (>= 2.5, != 2.5.1.1)
54
+ powerpack (~> 0.1)
55
+ rainbow (>= 2.2.2, < 4.0)
56
+ ruby-progressbar (~> 1.7)
57
+ unicode-display_width (~> 1.0, >= 1.0.1)
58
+ ruby-progressbar (1.10.0)
59
+ slop (4.6.2)
60
+ thread_safe (0.3.6)
61
+ tzinfo (1.2.5)
62
+ thread_safe (~> 0.1)
63
+ unicode-display_width (1.4.0)
64
+
65
+ PLATFORMS
66
+ ruby
67
+
68
+ DEPENDENCIES
69
+ bundler (~> 1.16)
70
+ rabbitek!
71
+ rake (~> 10.0)
72
+ rspec (~> 3.0)
73
+ rubocop (~> 0.58.0)
74
+
75
+ BUNDLED WITH
76
+ 1.16.4
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Boostcom
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # Rabbitek
2
+
3
+ High performance, easy to use background job processing library for Ruby using RabbitMQ queues.
4
+
5
+ * Consumers (servers) are configurable via yaml files.
6
+ * Retries
7
+ * Delayed jobs
8
+ * Jobs priority (through RabbitMQ Priority Queues)
9
+ * Scalable and failure-safe
10
+ * Client & Server hooks
11
+ * OpenTracing (http://opentracing.io/) instrumentation
12
+ * NewRelic instrumentation for sending errors
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem 'rabbitek'
20
+ ```
21
+
22
+ And then execute:
23
+
24
+ $ bundle
25
+
26
+ ## Usage
27
+
28
+ First, you need configuration file:
29
+
30
+ ```yaml
31
+ # config/rabbitek.yml
32
+ consumers:
33
+ - FirstConsumer
34
+ - SecondConsumer
35
+ threads: 25
36
+ parameters:
37
+ queue: myqueue
38
+ basic_qos: 20
39
+ queue_attributes:
40
+ arguments:
41
+ x-max-priority: 10
42
+ ```
43
+
44
+ Create consumer and include `Rabbitek::Consumer`, add `rabbit_options` with path to config file
45
+ and create `perform` method same way as on example:
46
+
47
+ ```
48
+ class ExampleConsumer
49
+ include Rabbitek::Consumer
50
+
51
+ rabbit_options config_file: 'config/rabbitek.yml'
52
+
53
+ def perform(payload, delivery_info, properties)
54
+ ack!(delivery_info)
55
+ end
56
+ end
57
+ ```
58
+
59
+ Lastly, run server:
60
+
61
+ ```
62
+ bundle exec rabbitek
63
+ ```
64
+
65
+ You can schedule jobs e.g.: `ExampleCustomer.perform_async(some: :payload)`
66
+
67
+
68
+ ## Roadmap
69
+
70
+ * more tests!
71
+ * dead queue
72
+ * CRON jobs
73
+ * job batching (run consumer with e.g. max 100 messages at once)
74
+ * extended docs and how to
75
+ * prometheus metrics
76
+
77
+
78
+ ## Development
79
+
80
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
81
+
82
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
83
+
84
+ ## Contributing
85
+
86
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Boostcom/rabbitek.
87
+
88
+ ## License
89
+
90
+ Please see [LICENSE.txt](LICENSE.txt)
91
+
92
+ ## Author
93
+
94
+ ![Boostcom](boostcom-logo.png)
95
+
96
+ **[Boostcom](https://boostcom.com/)** - we provide the most powerful management- and loyalty platform built for the needs of shopping centres.
97
+
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'rabbitek'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/boostcom-logo.png ADDED
Binary file
data/exe/rabbitek ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/rabbitek/cli'
5
+
6
+ begin
7
+ Rabbitek::CLI.new.run
8
+ rescue StandardError => e
9
+ raise(e) if $DEBUG
10
+ STDERR.puts(e.message)
11
+ STDERR.puts(e.backtrace.join("\n"))
12
+ exit(1)
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabbitek
4
+ ##
5
+ # Bunny connection setup
6
+ class BunnyConnection
7
+ def self.initialize_connection
8
+ connection = Bunny.new(Rabbitek.config.bunny_configuration)
9
+ connection.start
10
+ connection
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabbitek
4
+ class CLI
5
+ ##
6
+ # OS signal handlers
7
+ class SignalHandlers
8
+ SIGNALS = {
9
+ INT: :shutdown,
10
+ TERM: :shutdown
11
+ }.freeze
12
+
13
+ def self.setup(io_w)
14
+ SIGNALS.each do |signal, hook|
15
+ Signal.trap(signal) { io_w.write("#{hook}\n") }
16
+ end
17
+ end
18
+
19
+ def self.shutdown
20
+ raise Interrupt
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'slop'
4
+ require 'yaml'
5
+
6
+ require_relative './cli/signal_handlers'
7
+ require 'rabbitek'
8
+
9
+ module Rabbitek
10
+ ##
11
+ # Rabbitek server CLI
12
+ class CLI
13
+ include Loggable
14
+
15
+ def run
16
+ opts
17
+ require_application
18
+ map_consumer_workers!
19
+
20
+ start_log
21
+
22
+ io_r, io_w = IO.pipe
23
+ SignalHandlers.setup(io_w)
24
+
25
+ begin
26
+ consumers = boot_consumers
27
+
28
+ while io_resp = IO.select([io_r]) # rubocop:disable Lint/AssignmentInCondition
29
+ SignalHandlers.public_send(io_resp.first.first.gets.strip)
30
+ end
31
+ rescue Interrupt
32
+ execute_shutdown(consumers)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def start_log # rubocop:disable Metrics/AbcSize
39
+ info "Rabbit consumers '[#{configuration[:workers].map(&:to_s).join(', ')}]' started with PID #{Process.pid}"
40
+ info "Client hooks: [#{Rabbitek.config.client_hooks.map(&:class).join(', ')}]"
41
+ info "Server hooks: [#{Rabbitek.config.server_hooks.map(&:class).join(', ')}]"
42
+ end
43
+
44
+ def opts
45
+ @opts ||= Slop.parse do |o|
46
+ o.string '-c', '--config', 'config file path. Default: "config/rabbitek.yaml"', default: 'config/rabbitek.yml'
47
+ o.string '-r', '--require', 'file to require while booting. Default: "config/environment.rb"',
48
+ default: 'config/environment.rb'
49
+ o.on '--version', 'print the version' do
50
+ puts VERSION
51
+ exit
52
+ end
53
+ o.on '-h', '--help' do
54
+ puts o
55
+ exit
56
+ end
57
+ end
58
+ end
59
+
60
+ def configuration
61
+ @configuration ||= YAML.load_file(opts[:config]).with_indifferent_access
62
+ end
63
+
64
+ def require_application
65
+ require File.expand_path(opts[:require])
66
+ end
67
+
68
+ def map_consumer_workers!
69
+ configuration[:workers].map!(&:constantize)
70
+ end
71
+
72
+ def boot_consumers
73
+ (1..configuration[:threads]).each_with_object([]) do |_, arr|
74
+ arr << Starter.new(Rabbitek.bunny_connection, configuration).start
75
+ end
76
+ end
77
+
78
+ def execute_shutdown(consumers)
79
+ info 'Shutting down gracefully...'
80
+
81
+ consumers.map(&:cancel)
82
+ Rabbitek.close_bunny_connection
83
+ info 'Graceful shutdown completed'
84
+
85
+ exit(0)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabbitek
4
+ ##
5
+ # Base client hook class
6
+ class ClientHook
7
+ def call(payload, params)
8
+ yield(payload, params)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../client_hook'
4
+
5
+ module Rabbitek
6
+ module Client
7
+ module Hooks
8
+ ##
9
+ # OpenTracing client hook
10
+ class OpenTracing < Rabbitek::ClientHook
11
+ def call(payload, params)
12
+ result = nil
13
+
14
+ ::OpenTracing.start_active_span(params[:routing_key], opentracing_options(params)) do |scope|
15
+ begin
16
+ params[:headers] ||= {}
17
+ Utils::OpenTracing.inject!(scope.span, params[:headers])
18
+
19
+ result = super
20
+ rescue StandardError => e
21
+ raise unless scope.span
22
+
23
+ Utils::OpenTracing.log_error(scope.span, e)
24
+ raise
25
+ end
26
+ end
27
+
28
+ result
29
+ end
30
+
31
+ def opentracing_options(params)
32
+ Utils::OpenTracing.client_options(params)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabbitek
4
+ ##
5
+ # Handles publishing message to RabbitMQ
6
+ class Publisher
7
+ def initialize(exchange_name, exchange_type: 'direct', channel: nil)
8
+ @channel = channel || Rabbitek.create_channel
9
+ @exchange_name = exchange_name
10
+ @exchange_type = exchange_type
11
+ end
12
+
13
+ def publish(payload, params = {})
14
+ Utils::HookWalker.new(Rabbitek.config.client_hooks).call!(payload, params) do |parsed_payload, parsed_params|
15
+ exchange.publish(Utils::Oj.dump(parsed_payload), parsed_params)
16
+ end
17
+ end
18
+
19
+ def close
20
+ @channel.close
21
+ end
22
+
23
+ def exchange
24
+ @exchange ||= Utils::Common.exchange(@channel, @exchange_type, @exchange_name)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabbitek
4
+ ##
5
+ # Rabbitek configuration
6
+ class Config
7
+ DEFAULTS = {
8
+ bunny_configuration: { hosts: 'localhost:5672', vhost: '/' },
9
+ log_format: 'json',
10
+ enable_newrelic: true
11
+ }.freeze
12
+
13
+ attr_accessor(*DEFAULTS.keys)
14
+
15
+ def initialize
16
+ DEFAULTS.each { |k, v| public_send("#{k}=", v) }
17
+
18
+ @client_hooks_config = []
19
+ @server_hooks_config = []
20
+ end
21
+
22
+ def add_client_hook(hook_object)
23
+ @client_hooks_config << hook_object
24
+ end
25
+
26
+ def add_server_hook(hook_object)
27
+ @server_hooks_config << hook_object
28
+ end
29
+
30
+ def client_hooks
31
+ @client_hooks ||= begin
32
+ @client_hooks_config << Client::Hooks::OpenTracing.new
33
+ @client_hooks_config.reverse
34
+ end
35
+ end
36
+
37
+ def server_hooks
38
+ @server_hooks ||= begin
39
+ @server_hooks_config.unshift(Server::Hooks::TimeTracker.new)
40
+ @server_hooks_config.push(Server::Hooks::OpenTracing.new, Server::Hooks::Retry.new)
41
+ @server_hooks_config.reverse
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabbitek
4
+ ##
5
+ # Log helpers
6
+ module Loggable
7
+ def self.logger
8
+ Rabbitek.logger
9
+ end
10
+
11
+ def logger
12
+ Rabbitek.logger
13
+ end
14
+
15
+ def error(msg)
16
+ log_msg(:error, msg)
17
+ NewRelic::Agent.notice_error(msg) if Rabbitek.config.enable_newrelic && Object.const_defined?('NewRelic')
18
+ true
19
+ end
20
+
21
+ def warn(msg)
22
+ log_msg(:warn, msg)
23
+ end
24
+
25
+ def debug(msg)
26
+ log_msg(:debug, msg)
27
+ end
28
+
29
+ def info(msg)
30
+ log_msg(:info, msg)
31
+ end
32
+
33
+ private
34
+
35
+ def log_msg(severity, msg)
36
+ if logger.respond_to?(:tagged)
37
+ logger.tagged(class_name(msg)) { logger.send(severity, msg) }
38
+ else
39
+ logger.send(severity, msg)
40
+ end
41
+
42
+ true
43
+ end
44
+
45
+ def class_name(msg)
46
+ msg.is_a?(Hash) && msg[:class_name] ? msg.delete(:class_name) : self.class.name
47
+ end
48
+ end
49
+ end