kicks 3.0.0.pre
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 +7 -0
- data/.github/workflows/ci.yml +24 -0
- data/.gitignore +12 -0
- data/ChangeLog.md +142 -0
- data/Dockerfile +24 -0
- data/Dockerfile.slim +20 -0
- data/Gemfile +8 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +209 -0
- data/Rakefile +12 -0
- data/bin/sneakers +6 -0
- data/docker-compose.yml +24 -0
- data/examples/benchmark_worker.rb +22 -0
- data/examples/max_retry_handler.rb +68 -0
- data/examples/metrics_worker.rb +34 -0
- data/examples/middleware_worker.rb +36 -0
- data/examples/newrelic_metrics_worker.rb +40 -0
- data/examples/profiling_worker.rb +69 -0
- data/examples/sneakers.conf.rb.example +11 -0
- data/examples/title_scraper.rb +36 -0
- data/examples/workflow_worker.rb +23 -0
- data/kicks.gemspec +44 -0
- data/lib/sneakers/cli.rb +122 -0
- data/lib/sneakers/concerns/logging.rb +34 -0
- data/lib/sneakers/concerns/metrics.rb +34 -0
- data/lib/sneakers/configuration.rb +125 -0
- data/lib/sneakers/content_encoding.rb +47 -0
- data/lib/sneakers/content_type.rb +47 -0
- data/lib/sneakers/error_reporter.rb +33 -0
- data/lib/sneakers/errors.rb +2 -0
- data/lib/sneakers/handlers/maxretry.rb +219 -0
- data/lib/sneakers/handlers/oneshot.rb +26 -0
- data/lib/sneakers/metrics/logging_metrics.rb +16 -0
- data/lib/sneakers/metrics/newrelic_metrics.rb +32 -0
- data/lib/sneakers/metrics/null_metrics.rb +13 -0
- data/lib/sneakers/metrics/statsd_metrics.rb +21 -0
- data/lib/sneakers/middleware/config.rb +23 -0
- data/lib/sneakers/publisher.rb +49 -0
- data/lib/sneakers/queue.rb +87 -0
- data/lib/sneakers/runner.rb +91 -0
- data/lib/sneakers/spawner.rb +30 -0
- data/lib/sneakers/support/production_formatter.rb +11 -0
- data/lib/sneakers/support/utils.rb +18 -0
- data/lib/sneakers/tasks.rb +66 -0
- data/lib/sneakers/version.rb +3 -0
- data/lib/sneakers/worker.rb +162 -0
- data/lib/sneakers/workergroup.rb +60 -0
- data/lib/sneakers.rb +125 -0
- data/log/.gitkeep +0 -0
- data/scripts/local_integration +2 -0
- data/scripts/local_worker +3 -0
- data/spec/fixtures/integration_worker.rb +18 -0
- data/spec/fixtures/require_worker.rb +23 -0
- data/spec/gzip_helper.rb +15 -0
- data/spec/sneakers/cli_spec.rb +75 -0
- data/spec/sneakers/concerns/logging_spec.rb +39 -0
- data/spec/sneakers/concerns/metrics_spec.rb +38 -0
- data/spec/sneakers/configuration_spec.rb +97 -0
- data/spec/sneakers/content_encoding_spec.rb +81 -0
- data/spec/sneakers/content_type_spec.rb +81 -0
- data/spec/sneakers/integration_spec.rb +158 -0
- data/spec/sneakers/publisher_spec.rb +179 -0
- data/spec/sneakers/queue_spec.rb +169 -0
- data/spec/sneakers/runner_spec.rb +70 -0
- data/spec/sneakers/sneakers_spec.rb +77 -0
- data/spec/sneakers/support/utils_spec.rb +44 -0
- data/spec/sneakers/tasks/sneakers_run_spec.rb +115 -0
- data/spec/sneakers/worker_handlers_spec.rb +469 -0
- data/spec/sneakers/worker_spec.rb +712 -0
- data/spec/sneakers/workergroup_spec.rb +83 -0
- data/spec/spec_helper.rb +21 -0
- metadata +352 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
$: << File.expand_path('../lib', File.dirname(__FILE__))
|
2
|
+
require 'sneakers'
|
3
|
+
require 'sneakers/runner'
|
4
|
+
require 'sneakers/metrics/newrelic_metrics'
|
5
|
+
require 'open-uri'
|
6
|
+
require 'newrelic_rpm'
|
7
|
+
|
8
|
+
# With this configuration will send two types of data to newrelic server:
|
9
|
+
# 1. Transaction data which you would see under 'Applications'
|
10
|
+
# 2. Metrics where you will be able to see by configuring a dashboardi, available for enterprise accounts
|
11
|
+
#
|
12
|
+
# You should have newrelic.yml in the 'config' folder with the proper account settings
|
13
|
+
|
14
|
+
Sneakers::Metrics::NewrelicMetrics.eagent ::NewRelic
|
15
|
+
Sneakers.configure metrics: Sneakers::Metrics::NewrelicMetrics.new
|
16
|
+
|
17
|
+
class MetricsWorker
|
18
|
+
include Sneakers::Worker
|
19
|
+
include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
|
20
|
+
|
21
|
+
from_queue 'downloads'
|
22
|
+
|
23
|
+
def work(msg)
|
24
|
+
title = extract_title(open(msg))
|
25
|
+
logger.info "FOUND <#{title}>"
|
26
|
+
ack!
|
27
|
+
end
|
28
|
+
|
29
|
+
add_transaction_tracer :work, name: 'MetricsWorker', params: 'args[0]', category: :task
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def extract_title(html)
|
34
|
+
html =~ /<title>(.*?)<\/title>/
|
35
|
+
$1
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
r = Sneakers::Runner.new([ MetricsWorker ])
|
40
|
+
r.run
|
@@ -0,0 +1,69 @@
|
|
1
|
+
$: << File.expand_path('../lib', File.dirname(__FILE__))
|
2
|
+
require 'sneakers'
|
3
|
+
require 'sneakers/runner'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
|
7
|
+
profiling = ARGV[0]
|
8
|
+
messages = 100_000
|
9
|
+
|
10
|
+
|
11
|
+
if profiling
|
12
|
+
require 'ruby-prof'
|
13
|
+
messages /= 100 # profiling makes everything much slower (around 300req/s)
|
14
|
+
end
|
15
|
+
|
16
|
+
Sneakers.configure
|
17
|
+
Sneakers.logger.level = Logger::ERROR
|
18
|
+
|
19
|
+
Sneakers::Worker.configure_logger(Logger.new('/dev/null'))
|
20
|
+
|
21
|
+
puts "feeding messages in"
|
22
|
+
messages.times {
|
23
|
+
Sneakers.publish("{}", :to_queue => 'downloads')
|
24
|
+
}
|
25
|
+
puts "done"
|
26
|
+
|
27
|
+
|
28
|
+
class ProfilingWorker
|
29
|
+
include Sneakers::Worker
|
30
|
+
from_queue 'downloads',
|
31
|
+
:ack => true,
|
32
|
+
:threads => 50,
|
33
|
+
:prefetch => 50,
|
34
|
+
:timeout_job_after => 1,
|
35
|
+
:exchange => 'sneakers',
|
36
|
+
:heartbeat => 5,
|
37
|
+
:amqp_heartbeat => 10
|
38
|
+
def work(msg)
|
39
|
+
ack!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
r = Sneakers::Runner.new([ProfilingWorker])
|
46
|
+
|
47
|
+
# ctrl-c and Ruby 2.0 breaks signal handling
|
48
|
+
# Sidekiq has same issues
|
49
|
+
# https://github.com/mperham/sidekiq/issues/728
|
50
|
+
#
|
51
|
+
# so we use a timeout and a thread that kills profiling
|
52
|
+
if profiling
|
53
|
+
puts "profiling start"
|
54
|
+
RubyProf.start
|
55
|
+
|
56
|
+
|
57
|
+
Thread.new do
|
58
|
+
sleep 10
|
59
|
+
puts "stopping profiler"
|
60
|
+
result = RubyProf.stop
|
61
|
+
|
62
|
+
# Print a flat profile to text
|
63
|
+
printer = RubyProf::FlatPrinter.new(result)
|
64
|
+
printer.print(STDOUT)
|
65
|
+
exit(0)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
r.run
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "sneakers"
|
2
|
+
require 'open-uri'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
def compose_or_localhost(key)
|
6
|
+
Resolv::DNS.new.getaddress(key)
|
7
|
+
rescue
|
8
|
+
"localhost"
|
9
|
+
end
|
10
|
+
|
11
|
+
rmq_addr = compose_or_localhost("rabbitmq")
|
12
|
+
|
13
|
+
Sneakers.configure :log => STDOUT, :amqp => "amqp://guest:guest@#{rmq_addr}:5672"
|
14
|
+
Sneakers.logger.level = Logger::INFO
|
15
|
+
|
16
|
+
class TitleScraper
|
17
|
+
include Sneakers::Worker
|
18
|
+
|
19
|
+
from_queue 'downloads'
|
20
|
+
|
21
|
+
def work(msg)
|
22
|
+
title = extract_title(open(msg))
|
23
|
+
logger.info "FOUND <#{title}>"
|
24
|
+
ack!
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def extract_title(html)
|
30
|
+
html =~ /<title>(.*?)<\/title>/
|
31
|
+
$1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
$: << File.expand_path('../lib', File.dirname(__FILE__))
|
2
|
+
require 'sneakers'
|
3
|
+
|
4
|
+
class WorkflowWorker
|
5
|
+
include Sneakers::Worker
|
6
|
+
from_queue 'downloads',
|
7
|
+
exchange_options: { durable: false },
|
8
|
+
queue_options: { durable: false },
|
9
|
+
:ack => true,
|
10
|
+
:threads => 50,
|
11
|
+
:prefetch => 50,
|
12
|
+
:timeout_job_after => 1,
|
13
|
+
:exchange => 'dummy',
|
14
|
+
:heartbeat => 5,
|
15
|
+
:amqp_heartbeat => 10
|
16
|
+
|
17
|
+
def work(msg)
|
18
|
+
logger.info("Seriously, i'm DONE.")
|
19
|
+
publish "cleaned up", :to_queue => "foobar"
|
20
|
+
logger.info("Published to 'foobar'")
|
21
|
+
ack!
|
22
|
+
end
|
23
|
+
end
|
data/kicks.gemspec
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env gem build
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
lib = File.expand_path('../lib', __FILE__)
|
5
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
|
+
require 'sneakers/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |gem|
|
9
|
+
gem.name = 'kicks'
|
10
|
+
gem.version = Sneakers::VERSION
|
11
|
+
gem.authors = ['Dotan Nahum', 'Michael Klishin']
|
12
|
+
gem.email = ['michael@clojurewerkz.org']
|
13
|
+
gem.description = %q( Fast background processing framework for Ruby and RabbitMQ )
|
14
|
+
gem.summary = %q( Fast background processing framework for Ruby and RabbitMQ )
|
15
|
+
gem.homepage = 'https://github.com/ruby-amqp/kicks'
|
16
|
+
gem.license = 'MIT'
|
17
|
+
gem.required_ruby_version = Gem::Requirement.new(">= 2.5")
|
18
|
+
|
19
|
+
gem.files = `git ls-files`.split($/).reject { |f| f == 'Gemfile.lock' }
|
20
|
+
gem.executables = gem.files.grep(/^bin/).
|
21
|
+
reject { |f| f =~ /^bin\/ci/ }.
|
22
|
+
map { |f| File.basename(f) }
|
23
|
+
gem.test_files = gem.files.grep(/^(test|spec|features)\//)
|
24
|
+
gem.require_paths = ['lib']
|
25
|
+
|
26
|
+
gem.add_dependency 'serverengine', '~> 2.1'
|
27
|
+
gem.add_dependency 'bunny', '~> 2.19'
|
28
|
+
gem.add_dependency 'concurrent-ruby', '~> 1.0'
|
29
|
+
gem.add_dependency 'thor'
|
30
|
+
gem.add_dependency 'rake', '>= 12.3', '< 14.0'
|
31
|
+
|
32
|
+
# for integration environment (see .travis.yml and integration_spec)
|
33
|
+
gem.add_development_dependency 'rabbitmq_http_api_client'
|
34
|
+
gem.add_development_dependency 'redis'
|
35
|
+
|
36
|
+
gem.add_development_dependency 'minitest', '~> 5.15'
|
37
|
+
gem.add_development_dependency 'rr', '~> 3.0'
|
38
|
+
gem.add_development_dependency 'unparser', '~> 0.2'
|
39
|
+
gem.add_development_dependency 'simplecov', '~> 0.21'
|
40
|
+
gem.add_development_dependency 'simplecov-rcov-text'
|
41
|
+
gem.add_development_dependency 'guard', '~> 2.18'
|
42
|
+
gem.add_development_dependency 'guard-minitest', '~> 2.4'
|
43
|
+
gem.add_development_dependency 'pry-byebug', '~> 3.9'
|
44
|
+
end
|
data/lib/sneakers/cli.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'sneakers/runner'
|
3
|
+
|
4
|
+
|
5
|
+
#
|
6
|
+
# $ sneakers work TitleWorker,FooWorker
|
7
|
+
# $ sneakers stop
|
8
|
+
# $ sneakers recycle
|
9
|
+
# $ sneakers reload
|
10
|
+
# $ sneakers init
|
11
|
+
#
|
12
|
+
#
|
13
|
+
module Sneakers
|
14
|
+
class CLI < Thor
|
15
|
+
|
16
|
+
SNEAKERS=<<-EOF
|
17
|
+
|
18
|
+
__
|
19
|
+
,--' > Sneakers
|
20
|
+
`=====
|
21
|
+
|
22
|
+
EOF
|
23
|
+
|
24
|
+
BANNER = SNEAKERS
|
25
|
+
|
26
|
+
method_option :debug
|
27
|
+
method_option :daemonize
|
28
|
+
method_option :log
|
29
|
+
method_option :pid_path
|
30
|
+
method_option :require
|
31
|
+
|
32
|
+
desc "work FirstWorker,SecondWorker ... ,NthWorker", "Run workers"
|
33
|
+
def work(workers = "")
|
34
|
+
opts = {
|
35
|
+
:daemonize => !!options[:daemonize]
|
36
|
+
}
|
37
|
+
|
38
|
+
opts[:log] = options[:log] || (opts[:daemonize] ? 'sneakers.log' : STDOUT)
|
39
|
+
opts[:pid_path] = options[:pid_path] if options[:pid_path]
|
40
|
+
|
41
|
+
if opts[:daemonize]
|
42
|
+
puts "*** DEPRACATED: self-daemonization '--daemonize' is considered a bad practice, which is why this feature will be removed in future versions. Please run Sneakers in front, and use things like upstart, systemd, or supervisor to manage it as a daemon."
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
Sneakers.configure(opts)
|
47
|
+
|
48
|
+
require_boot File.expand_path(options[:require]) if options[:require]
|
49
|
+
|
50
|
+
if workers.empty?
|
51
|
+
workers = Sneakers::Worker::Classes
|
52
|
+
else
|
53
|
+
workers, missing_workers = Sneakers::Utils.parse_workers(workers)
|
54
|
+
end
|
55
|
+
|
56
|
+
unless missing_workers.nil? || missing_workers.empty?
|
57
|
+
say "Missing workers: #{missing_workers.join(', ')}" if missing_workers
|
58
|
+
say "Did you `require` properly?"
|
59
|
+
return
|
60
|
+
end
|
61
|
+
|
62
|
+
if workers.empty?
|
63
|
+
say <<-EOF
|
64
|
+
Error: No workers found.
|
65
|
+
Please require your worker classes before specifying in CLI
|
66
|
+
|
67
|
+
$ sneakers work FooWorker
|
68
|
+
^- require this in your code
|
69
|
+
|
70
|
+
EOF
|
71
|
+
return
|
72
|
+
end
|
73
|
+
|
74
|
+
r = Sneakers::Runner.new(workers)
|
75
|
+
|
76
|
+
pid = Sneakers::CONFIG[:pid_path]
|
77
|
+
|
78
|
+
say SNEAKERS
|
79
|
+
say "Workers ....: #{em workers.join(', ')}"
|
80
|
+
say "Log ........: #{em (Sneakers::CONFIG[:log] == STDOUT ? 'Console' : Sneakers::CONFIG[:log]) }"
|
81
|
+
say "PID ........: #{em pid}"
|
82
|
+
say ""
|
83
|
+
say (" "*31)+"Process control"
|
84
|
+
say "="*80
|
85
|
+
say "Stop (nicely) ..............: kill -SIGTERM `cat #{pid}`"
|
86
|
+
say "Stop (immediate) ...........: kill -SIGQUIT `cat #{pid}`"
|
87
|
+
say "Restart (nicely) ...........: kill -SIGUSR1 `cat #{pid}`"
|
88
|
+
say "Restart (immediate) ........: kill -SIGHUP `cat #{pid}`"
|
89
|
+
say "Reconfigure ................: kill -SIGUSR2 `cat #{pid}`"
|
90
|
+
say "Scale workers ..............: reconfigure, then restart"
|
91
|
+
say "="*80
|
92
|
+
say ""
|
93
|
+
|
94
|
+
if options[:debug]
|
95
|
+
say "==== configuration ==="
|
96
|
+
say Sneakers::CONFIG.inspect
|
97
|
+
say "======================"
|
98
|
+
end
|
99
|
+
|
100
|
+
r.run
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
private
|
105
|
+
def require_boot(file)
|
106
|
+
load file
|
107
|
+
end
|
108
|
+
|
109
|
+
def em(text)
|
110
|
+
shell.set_color(text, nil, true)
|
111
|
+
end
|
112
|
+
|
113
|
+
def ok(detail=nil)
|
114
|
+
text = detail ? "OK, #{detail}." : "OK."
|
115
|
+
say text, :green
|
116
|
+
end
|
117
|
+
|
118
|
+
def error(detail)
|
119
|
+
say detail, :red
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Sneakers
|
2
|
+
module Concerns
|
3
|
+
module Logging
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
base.send :define_method, :logger do
|
7
|
+
base.logger
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def logger
|
13
|
+
@logger
|
14
|
+
end
|
15
|
+
|
16
|
+
def logger=(logger)
|
17
|
+
@logger = logger
|
18
|
+
end
|
19
|
+
|
20
|
+
def configure_logger(log=nil)
|
21
|
+
if log
|
22
|
+
@logger = log
|
23
|
+
else
|
24
|
+
@logger = Logger.new(STDOUT)
|
25
|
+
@logger.level = Logger::INFO
|
26
|
+
@logger.formatter = Sneakers::Support::ProductionFormatter
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'sneakers/metrics/null_metrics'
|
2
|
+
|
3
|
+
module Sneakers
|
4
|
+
module Concerns
|
5
|
+
module Metrics
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
base.send :define_method, :metrics do
|
9
|
+
base.metrics
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def metrics
|
15
|
+
@metrics
|
16
|
+
end
|
17
|
+
|
18
|
+
def metrics=(metrics)
|
19
|
+
@metrics = metrics
|
20
|
+
end
|
21
|
+
|
22
|
+
def configure_metrics(metrics=nil)
|
23
|
+
if metrics
|
24
|
+
@metrics = metrics
|
25
|
+
else
|
26
|
+
@metrics = Sneakers::Metrics::NullMetrics.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'sneakers/error_reporter'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module Sneakers
|
5
|
+
class Configuration
|
6
|
+
|
7
|
+
extend Forwardable
|
8
|
+
def_delegators :@hash, :to_hash, :[], :[]=, :==, :fetch, :delete, :has_key?
|
9
|
+
|
10
|
+
EXCHANGE_OPTION_DEFAULTS = {
|
11
|
+
:type => :direct,
|
12
|
+
:durable => true,
|
13
|
+
:auto_delete => false,
|
14
|
+
:arguments => {} # Passed as :arguments to Bunny::Channel#exchange
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
QUEUE_OPTION_DEFAULTS = {
|
18
|
+
:durable => true,
|
19
|
+
:auto_delete => false,
|
20
|
+
:exclusive => false,
|
21
|
+
:arguments => {}
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
DEFAULTS = {
|
25
|
+
# Set up default handler which just logs the error.
|
26
|
+
# Remove this in production if you don't want sensitive data logged.
|
27
|
+
:error_reporters => [Sneakers::ErrorReporter::DefaultLogger.new],
|
28
|
+
|
29
|
+
# runner
|
30
|
+
:runner_config_file => nil,
|
31
|
+
:metrics => nil,
|
32
|
+
:daemonize => false,
|
33
|
+
:start_worker_delay => 0.2,
|
34
|
+
:workers => 4,
|
35
|
+
:log => STDOUT,
|
36
|
+
:pid_path => 'sneakers.pid',
|
37
|
+
:amqp_heartbeat => 30,
|
38
|
+
|
39
|
+
# workers
|
40
|
+
:prefetch => 10,
|
41
|
+
:threads => 10,
|
42
|
+
:share_threads => false,
|
43
|
+
:ack => true,
|
44
|
+
:heartbeat => 30,
|
45
|
+
:hooks => {},
|
46
|
+
:exchange => 'sneakers',
|
47
|
+
:exchange_options => EXCHANGE_OPTION_DEFAULTS,
|
48
|
+
:queue_options => QUEUE_OPTION_DEFAULTS
|
49
|
+
}.freeze
|
50
|
+
|
51
|
+
|
52
|
+
def initialize
|
53
|
+
clear
|
54
|
+
end
|
55
|
+
|
56
|
+
def clear
|
57
|
+
@hash = DEFAULTS.dup
|
58
|
+
@hash[:amqp] = ENV.fetch('RABBITMQ_URL', 'amqp://guest:guest@localhost:5672')
|
59
|
+
@hash[:vhost] = AMQ::Settings.parse_amqp_url(@hash[:amqp]).fetch(:vhost, '/')
|
60
|
+
end
|
61
|
+
|
62
|
+
def merge!(hash)
|
63
|
+
hash = hash.dup
|
64
|
+
hash = map_all_deprecated_options(hash)
|
65
|
+
|
66
|
+
# parse vhost from amqp if vhost is not specified explicitly, only
|
67
|
+
# if we're not given a connection to use.
|
68
|
+
if hash[:connection].nil?
|
69
|
+
if hash[:vhost].nil? && !hash[:amqp].nil?
|
70
|
+
hash[:vhost] = AMQ::Settings.parse_amqp_url(hash[:amqp]).fetch(:vhost, '/')
|
71
|
+
end
|
72
|
+
else
|
73
|
+
# If we are given a Bunny object, ignore params we'd otherwise use to
|
74
|
+
# create one. This removes any question about where config params are
|
75
|
+
# coming from, and makes it more likely that downstream code that needs
|
76
|
+
# this info gets it from the right place.
|
77
|
+
[:vhost, :amqp, :heartbeat].each do |k|
|
78
|
+
hash.delete(k)
|
79
|
+
@hash.delete(k)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
@hash = deep_merge(@hash, hash)
|
84
|
+
end
|
85
|
+
|
86
|
+
def merge(hash)
|
87
|
+
instance = self.class.new
|
88
|
+
instance.merge! to_hash
|
89
|
+
instance.merge! hash
|
90
|
+
instance
|
91
|
+
end
|
92
|
+
|
93
|
+
def inspect_with_redaction
|
94
|
+
redacted = self.class.new
|
95
|
+
redacted.merge! to_hash
|
96
|
+
|
97
|
+
# redact passwords
|
98
|
+
redacted[:amqp] = redacted[:amqp].sub(/(?<=\Aamqp:\/)[^@]+(?=@)/, "<redacted>") if redacted.has_key?(:amqp)
|
99
|
+
return redacted.inspect_without_redaction
|
100
|
+
end
|
101
|
+
alias_method :inspect_without_redaction, :inspect
|
102
|
+
alias_method :inspect, :inspect_with_redaction
|
103
|
+
|
104
|
+
def map_all_deprecated_options(hash)
|
105
|
+
hash = map_deprecated_options_key(:exchange_options, :exchange_type, :type, true, hash)
|
106
|
+
hash = map_deprecated_options_key(:exchange_options, :exchange_arguments, :arguments, true, hash)
|
107
|
+
hash = map_deprecated_options_key(:exchange_options, :durable, :durable, false, hash)
|
108
|
+
hash = map_deprecated_options_key(:queue_options, :durable, :durable, true, hash)
|
109
|
+
hash = map_deprecated_options_key(:queue_options, :arguments, :arguments, true, hash)
|
110
|
+
hash
|
111
|
+
end
|
112
|
+
|
113
|
+
def map_deprecated_options_key(target_key, deprecated_key, key, delete_deprecated_key, hash = {})
|
114
|
+
return hash if hash[deprecated_key].nil?
|
115
|
+
hash = deep_merge({ target_key => { key => hash[deprecated_key] } }, hash)
|
116
|
+
hash.delete(deprecated_key) if delete_deprecated_key
|
117
|
+
hash
|
118
|
+
end
|
119
|
+
|
120
|
+
def deep_merge(first, second)
|
121
|
+
merger = proc { |_, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
122
|
+
first.merge(second, &merger)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Sneakers
|
2
|
+
class ContentEncoding
|
3
|
+
def self.register(content_encoding: nil, encoder: nil, decoder: nil)
|
4
|
+
# This can be removed when support is dropped for ruby 2.0 and replaced
|
5
|
+
# by a keyword arg with no default value
|
6
|
+
fail ArgumentError, 'missing keyword: content_encoding' if content_encoding.nil?
|
7
|
+
fail ArgumentError, 'missing keyword: encoder' if encoder.nil?
|
8
|
+
fail ArgumentError, 'missing keyword: decoder' if decoder.nil?
|
9
|
+
|
10
|
+
fail ArgumentError, "#{content_encoding} encoder must be a proc" unless encoder.is_a? Proc
|
11
|
+
fail ArgumentError, "#{content_encoding} decoder must be a proc" unless decoder.is_a? Proc
|
12
|
+
|
13
|
+
fail ArgumentError, "#{content_encoding} encoder must accept one argument, the payload" unless encoder.arity == 1
|
14
|
+
fail ArgumentError, "#{content_encoding} decoder must accept one argument, the payload" unless decoder.arity == 1
|
15
|
+
@_encodings[content_encoding] = new(encoder, decoder)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.encode(payload, content_encoding)
|
19
|
+
return payload unless content_encoding
|
20
|
+
@_encodings[content_encoding].encoder.(payload)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.decode(payload, content_encoding)
|
24
|
+
return payload unless content_encoding
|
25
|
+
@_encodings[content_encoding].decoder.(payload)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.reset!
|
29
|
+
@_encodings = Hash.new(
|
30
|
+
new(passthrough, passthrough)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.passthrough
|
35
|
+
->(payload) { payload }
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(encoder, decoder)
|
39
|
+
@encoder = encoder
|
40
|
+
@decoder = decoder
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :encoder, :decoder
|
44
|
+
|
45
|
+
reset!
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Sneakers
|
2
|
+
class ContentType
|
3
|
+
def self.register(content_type: nil, serializer: nil, deserializer: nil)
|
4
|
+
# This can be removed when support is dropped for ruby 2.0 and replaced
|
5
|
+
# by a keyword arg with no default value
|
6
|
+
fail ArgumentError, 'missing keyword: content_type' if content_type.nil?
|
7
|
+
fail ArgumentError, 'missing keyword: serializer' if serializer.nil?
|
8
|
+
fail ArgumentError, 'missing keyword: deserializer' if deserializer.nil?
|
9
|
+
|
10
|
+
fail ArgumentError, "#{content_type} serializer must be a proc" unless serializer.is_a? Proc
|
11
|
+
fail ArgumentError, "#{content_type} deserializer must be a proc" unless deserializer.is_a? Proc
|
12
|
+
|
13
|
+
fail ArgumentError, "#{content_type} serializer must accept one argument, the payload" unless serializer.arity == 1
|
14
|
+
fail ArgumentError, "#{content_type} deserializer must accept one argument, the payload" unless deserializer.arity == 1
|
15
|
+
@_types[content_type] = new(serializer, deserializer)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.serialize(payload, content_type)
|
19
|
+
return payload unless content_type
|
20
|
+
@_types[content_type].serializer.(payload)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.deserialize(payload, content_type)
|
24
|
+
return payload unless content_type
|
25
|
+
@_types[content_type].deserializer.(payload)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.reset!
|
29
|
+
@_types = Hash.new(
|
30
|
+
new(passthrough, passthrough)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.passthrough
|
35
|
+
->(payload) { payload }
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(serializer, deserializer)
|
39
|
+
@serializer = serializer
|
40
|
+
@deserializer = deserializer
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :serializer, :deserializer
|
44
|
+
|
45
|
+
reset!
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Ripped off from https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/exception_handler.rb
|
2
|
+
module Sneakers
|
3
|
+
module ErrorReporter
|
4
|
+
class DefaultLogger
|
5
|
+
def call(exception, worker, context_hash)
|
6
|
+
Sneakers.logger.warn(context_hash) unless context_hash.empty?
|
7
|
+
log_string = ''
|
8
|
+
log_string += "[Exception error=#{exception.message.inspect} error_class=#{exception.class} worker_class=#{worker.class}" unless exception.nil?
|
9
|
+
log_string += " backtrace=#{exception.backtrace.take(50).join(',')}" unless exception.nil? || exception.backtrace.nil?
|
10
|
+
log_string += ']'
|
11
|
+
Sneakers.logger.error log_string
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def worker_error(exception, context_hash = {})
|
16
|
+
Sneakers.error_reporters.each do |handler|
|
17
|
+
begin
|
18
|
+
handler.call(exception, self, context_hash)
|
19
|
+
rescue SignalException, SystemExit
|
20
|
+
# ServerEngine handles these exceptions, so they are not expected
|
21
|
+
# to be raised within the error reporter.
|
22
|
+
# Nevertheless, they are listed here to ensure that they are not
|
23
|
+
# caught by the rescue block below.
|
24
|
+
raise
|
25
|
+
rescue Exception => inner_exception
|
26
|
+
Sneakers.logger.error '!!! ERROR REPORTER THREW AN ERROR !!!'
|
27
|
+
Sneakers.logger.error inner_exception
|
28
|
+
Sneakers.logger.error inner_exception.backtrace.join("\n") unless inner_exception.backtrace.nil?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|