sneakers_custom_bunny 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +20 -0
- data/Gemfile +3 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +172 -0
- data/ROADMAP.md +18 -0
- data/Rakefile +11 -0
- data/bin/sneakers +5 -0
- data/examples/benchmark_worker.rb +20 -0
- data/examples/max_retry_handler.rb +78 -0
- data/examples/metrics_worker.rb +28 -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 +23 -0
- data/examples/workflow_worker.rb +23 -0
- data/lib/sneakers.rb +83 -0
- data/lib/sneakers/cli.rb +115 -0
- data/lib/sneakers/concerns/logging.rb +34 -0
- data/lib/sneakers/concerns/metrics.rb +34 -0
- data/lib/sneakers/configuration.rb +59 -0
- data/lib/sneakers/handlers/maxretry.rb +191 -0
- data/lib/sneakers/handlers/oneshot.rb +30 -0
- data/lib/sneakers/metrics/logging_metrics.rb +16 -0
- data/lib/sneakers/metrics/newrelic_metrics.rb +37 -0
- data/lib/sneakers/metrics/null_metrics.rb +13 -0
- data/lib/sneakers/metrics/statsd_metrics.rb +21 -0
- data/lib/sneakers/publisher.rb +34 -0
- data/lib/sneakers/queue.rb +65 -0
- data/lib/sneakers/runner.rb +82 -0
- data/lib/sneakers/spawner.rb +27 -0
- data/lib/sneakers/support/production_formatter.rb +11 -0
- data/lib/sneakers/support/utils.rb +18 -0
- data/lib/sneakers/tasks.rb +34 -0
- data/lib/sneakers/version.rb +3 -0
- data/lib/sneakers/worker.rb +151 -0
- data/lib/sneakers/workergroup.rb +47 -0
- data/sneakers.gemspec +35 -0
- data/spec/fixtures/require_worker.rb +17 -0
- data/spec/sneakers/cli_spec.rb +63 -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 +75 -0
- data/spec/sneakers/publisher_spec.rb +83 -0
- data/spec/sneakers/queue_spec.rb +115 -0
- data/spec/sneakers/runner_spec.rb +26 -0
- data/spec/sneakers/sneakers_spec.rb +75 -0
- data/spec/sneakers/support/utils_spec.rb +44 -0
- data/spec/sneakers/worker_handlers_spec.rb +390 -0
- data/spec/sneakers/worker_spec.rb +463 -0
- data/spec/spec_helper.rb +13 -0
- metadata +306 -0
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Sneakers
|
5
|
+
module Handlers
|
6
|
+
#
|
7
|
+
# Maxretry uses dead letter policies on Rabbitmq to requeue and retry
|
8
|
+
# messages after failure (rejections, errors and timeouts). When the maximum
|
9
|
+
# number of retries is reached it will put the message on an error queue.
|
10
|
+
# This handler will only retry at the queue level. To accomplish that, the
|
11
|
+
# setup is a bit complex.
|
12
|
+
#
|
13
|
+
# Input:
|
14
|
+
# worker_exchange (eXchange)
|
15
|
+
# worker_queue (Queue)
|
16
|
+
# We create:
|
17
|
+
# worker_queue-retry - (X) where we setup the worker queue to dead-letter.
|
18
|
+
# worker_queue-retry - (Q) queue bound to ^ exchange, dead-letters to
|
19
|
+
# worker_queue-retry-requeue.
|
20
|
+
# worker_queue-error - (X) where to send max-retry failures
|
21
|
+
# worker_queue-error - (Q) bound to worker_queue-error.
|
22
|
+
# worker_queue-retry-requeue - (X) exchange to bind worker_queue to for
|
23
|
+
# requeuing directly to the worker_queue.
|
24
|
+
#
|
25
|
+
# This requires that you setup arguments to the worker queue to line up the
|
26
|
+
# dead letter queue. See the example for more information.
|
27
|
+
#
|
28
|
+
# Many of these can be override with options:
|
29
|
+
# - retry_exchange - sets retry exchange & queue
|
30
|
+
# - retry_error_exchange - sets error exchange and queue
|
31
|
+
# - retry_requeue_exchange - sets the exchange created to re-queue things
|
32
|
+
# back to the worker queue.
|
33
|
+
#
|
34
|
+
class Maxretry
|
35
|
+
|
36
|
+
def initialize(channel, queue, opts)
|
37
|
+
@worker_queue_name = queue.name
|
38
|
+
Sneakers.logger.debug do
|
39
|
+
"#{log_prefix} creating handler, opts=#{opts}"
|
40
|
+
end
|
41
|
+
|
42
|
+
@channel = channel
|
43
|
+
@opts = opts
|
44
|
+
|
45
|
+
# Construct names, defaulting where suitable
|
46
|
+
retry_name = @opts[:retry_exchange] || "#{@worker_queue_name}-retry"
|
47
|
+
error_name = @opts[:retry_error_exchange] || "#{@worker_queue_name}-error"
|
48
|
+
requeue_name = @opts[:retry_requeue_exchange] || "#{@worker_queue_name}-retry-requeue"
|
49
|
+
|
50
|
+
# Create the exchanges
|
51
|
+
@retry_exchange, @error_exchange, @requeue_exchange = [retry_name, error_name, requeue_name].map do |name|
|
52
|
+
Sneakers.logger.debug { "#{log_prefix} creating exchange=#{name}" }
|
53
|
+
@channel.exchange(name,
|
54
|
+
:type => 'topic',
|
55
|
+
:durable => opts[:durable])
|
56
|
+
end
|
57
|
+
|
58
|
+
# Create the queues and bindings
|
59
|
+
Sneakers.logger.debug do
|
60
|
+
"#{log_prefix} creating queue=#{retry_name} x-dead-letter-exchange=#{requeue_name}"
|
61
|
+
end
|
62
|
+
@retry_queue = @channel.queue(retry_name,
|
63
|
+
:durable => opts[:durable],
|
64
|
+
:arguments => {
|
65
|
+
:'x-dead-letter-exchange' => requeue_name,
|
66
|
+
:'x-message-ttl' => @opts[:retry_timeout] || 60000
|
67
|
+
})
|
68
|
+
@retry_queue.bind(@retry_exchange, :routing_key => '#')
|
69
|
+
|
70
|
+
Sneakers.logger.debug do
|
71
|
+
"#{log_prefix} creating queue=#{error_name}"
|
72
|
+
end
|
73
|
+
@error_queue = @channel.queue(error_name,
|
74
|
+
:durable => opts[:durable])
|
75
|
+
@error_queue.bind(@error_exchange, :routing_key => '#')
|
76
|
+
|
77
|
+
# Finally, bind the worker queue to our requeue exchange
|
78
|
+
queue.bind(@requeue_exchange, :routing_key => '#')
|
79
|
+
|
80
|
+
@max_retries = @opts[:retry_max_times] || 5
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
def acknowledge(hdr, props, msg)
|
85
|
+
@channel.acknowledge(hdr.delivery_tag, false)
|
86
|
+
end
|
87
|
+
|
88
|
+
def reject(hdr, props, msg, requeue = false)
|
89
|
+
if requeue
|
90
|
+
# This was explicitly rejected specifying it be requeued so we do not
|
91
|
+
# want it to pass through our retry logic.
|
92
|
+
@channel.reject(hdr.delivery_tag, requeue)
|
93
|
+
else
|
94
|
+
handle_retry(hdr, props, msg, :reject)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
def error(hdr, props, msg, err)
|
100
|
+
handle_retry(hdr, props, msg, err)
|
101
|
+
end
|
102
|
+
|
103
|
+
def timeout(hdr, props, msg)
|
104
|
+
handle_retry(hdr, props, msg, :timeout)
|
105
|
+
end
|
106
|
+
|
107
|
+
def noop(hdr, props, msg)
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
# Helper logic for retry handling. This will reject the message if there
|
112
|
+
# are remaining retries left on it, otherwise it will publish it to the
|
113
|
+
# error exchange along with the reason.
|
114
|
+
# @param hdr [Bunny::DeliveryInfo]
|
115
|
+
# @param props [Bunny::MessageProperties]
|
116
|
+
# @param msg [String] The message
|
117
|
+
# @param reason [String, Symbol, Exception] Reason for the retry, included
|
118
|
+
# in the JSON we put on the error exchange.
|
119
|
+
def handle_retry(hdr, props, msg, reason)
|
120
|
+
# +1 for the current attempt
|
121
|
+
num_attempts = failure_count(props[:headers]) + 1
|
122
|
+
if num_attempts <= @max_retries
|
123
|
+
# We call reject which will route the message to the
|
124
|
+
# x-dead-letter-exchange (ie. retry exchange) on the queue
|
125
|
+
Sneakers.logger.info do
|
126
|
+
"#{log_prefix} msg=retrying, count=#{num_attempts}, headers=#{props[:headers]}"
|
127
|
+
end
|
128
|
+
@channel.reject(hdr.delivery_tag, false)
|
129
|
+
# TODO: metrics
|
130
|
+
else
|
131
|
+
# Retried more than the max times
|
132
|
+
# Publish the original message with the routing_key to the error exchange
|
133
|
+
Sneakers.logger.info do
|
134
|
+
"#{log_prefix} msg=failing, retry_count=#{num_attempts}, reason=#{reason}"
|
135
|
+
end
|
136
|
+
data = {
|
137
|
+
error: reason,
|
138
|
+
num_attempts: num_attempts,
|
139
|
+
failed_at: Time.now.iso8601,
|
140
|
+
payload: Base64.encode64(msg.to_s)
|
141
|
+
}.tap do |hash|
|
142
|
+
if reason.is_a?(Exception)
|
143
|
+
hash[:error_class] = reason.class
|
144
|
+
hash[:error_message] = "#{reason}"
|
145
|
+
if reason.backtrace
|
146
|
+
hash[:backtrace] = reason.backtrace.take(10).join(', ')
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end.to_json
|
150
|
+
@error_exchange.publish(data, :routing_key => hdr.routing_key)
|
151
|
+
@channel.acknowledge(hdr.delivery_tag, false)
|
152
|
+
# TODO: metrics
|
153
|
+
end
|
154
|
+
end
|
155
|
+
private :handle_retry
|
156
|
+
|
157
|
+
# Uses the x-death header to determine the number of failures this job has
|
158
|
+
# seen in the past. This does not count the current failure. So for
|
159
|
+
# instance, the first time the job fails, this will return 0, the second
|
160
|
+
# time, 1, etc.
|
161
|
+
# @param headers [Hash] Hash of headers that Rabbit delivers as part of
|
162
|
+
# the message
|
163
|
+
# @return [Integer] Count of number of failures.
|
164
|
+
def failure_count(headers)
|
165
|
+
if headers.nil? || headers['x-death'].nil?
|
166
|
+
0
|
167
|
+
else
|
168
|
+
x_death_array = headers['x-death'].select do |x_death|
|
169
|
+
x_death['queue'] == @worker_queue_name
|
170
|
+
end
|
171
|
+
if x_death_array.count > 0 && x_death_array.first['count']
|
172
|
+
# Newer versions of RabbitMQ return headers with a count key
|
173
|
+
x_death_array.inject(0) {|sum, x_death| sum + x_death['count']}
|
174
|
+
else
|
175
|
+
# Older versions return a separate x-death header for each failure
|
176
|
+
x_death_array.count
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
private :failure_count
|
181
|
+
|
182
|
+
# Prefix all of our log messages so they are easier to find. We don't have
|
183
|
+
# the worker, so the next best thing is the queue name.
|
184
|
+
def log_prefix
|
185
|
+
"Maxretry handler [queue=#{@worker_queue_name}]"
|
186
|
+
end
|
187
|
+
private :log_prefix
|
188
|
+
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Sneakers
|
2
|
+
module Handlers
|
3
|
+
class Oneshot
|
4
|
+
def initialize(channel, queue, opts)
|
5
|
+
@channel = channel
|
6
|
+
@opts = opts
|
7
|
+
end
|
8
|
+
|
9
|
+
def acknowledge(hdr, props, msg)
|
10
|
+
@channel.acknowledge(hdr.delivery_tag, false)
|
11
|
+
end
|
12
|
+
|
13
|
+
def reject(hdr, props, msg, requeue=false)
|
14
|
+
@channel.reject(hdr.delivery_tag, requeue)
|
15
|
+
end
|
16
|
+
|
17
|
+
def error(hdr, props, msg, err)
|
18
|
+
reject(hdr, props, msg)
|
19
|
+
end
|
20
|
+
|
21
|
+
def timeout(hdr, props, msg)
|
22
|
+
reject(hdr, props, msg)
|
23
|
+
end
|
24
|
+
|
25
|
+
def noop(hdr, props, msg)
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Sneakers
|
2
|
+
module Metrics
|
3
|
+
class LoggingMetrics
|
4
|
+
def increment(metric)
|
5
|
+
Sneakers.logger.info("INC: #{metric}")
|
6
|
+
end
|
7
|
+
|
8
|
+
def timing(metric, &block)
|
9
|
+
start = Time.now
|
10
|
+
block.call
|
11
|
+
Sneakers.logger.info("TIME: #{metric} #{Time.now - start}")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Sneakers
|
2
|
+
module Metrics
|
3
|
+
class NewrelicMetrics
|
4
|
+
|
5
|
+
def self.eagent(eagent = nil)
|
6
|
+
@eagent = eagent || @eagent
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize()
|
10
|
+
#@connection = conn
|
11
|
+
end
|
12
|
+
|
13
|
+
def increment(metric)
|
14
|
+
record_stat metric, 1
|
15
|
+
end
|
16
|
+
|
17
|
+
def record_stat(metric, num)
|
18
|
+
stats(metric).record_data_point(num)
|
19
|
+
rescue Exception => e
|
20
|
+
puts "NewrelicMetrics#record_stat: #{e}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def timing(metric, &block)
|
24
|
+
start = Time.now
|
25
|
+
block.call
|
26
|
+
record_stat(metric, ((Time.now - start)*1000).floor)
|
27
|
+
end
|
28
|
+
|
29
|
+
def stats(metric)
|
30
|
+
metric.gsub! "\.", "\/"
|
31
|
+
NewrelicMetrics.eagent::Agent.get_stats("Custom/#{metric}")
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Sneakers
|
2
|
+
module Metrics
|
3
|
+
class StatsdMetrics
|
4
|
+
def initialize(conn)
|
5
|
+
@connection = conn
|
6
|
+
end
|
7
|
+
|
8
|
+
def increment(metric)
|
9
|
+
@connection.increment(metric)
|
10
|
+
end
|
11
|
+
|
12
|
+
def timing(metric, &block)
|
13
|
+
start = Time.now
|
14
|
+
block.call
|
15
|
+
@connection.timing(metric, ((Time.now - start)*1000).floor)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Sneakers
|
2
|
+
class Publisher
|
3
|
+
def initialize(opts = {})
|
4
|
+
@mutex = Mutex.new
|
5
|
+
@opts = Sneakers::CONFIG.merge(opts)
|
6
|
+
end
|
7
|
+
|
8
|
+
def publish(msg, options = {})
|
9
|
+
@mutex.synchronize do
|
10
|
+
ensure_connection! unless connected?
|
11
|
+
end
|
12
|
+
to_queue = options.delete(:to_queue)
|
13
|
+
options[:routing_key] ||= to_queue
|
14
|
+
Sneakers.logger.info {"publishing <#{msg}> to [#{options[:routing_key]}]"}
|
15
|
+
@exchange.publish(msg, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
attr_reader :exchange
|
20
|
+
|
21
|
+
private
|
22
|
+
def ensure_connection!
|
23
|
+
@bunny = Bunny.new(@opts[:amqp], heartbeat: @opts[:heartbeat], vhost: @opts[:vhost], :logger => Sneakers::logger)
|
24
|
+
@bunny.start
|
25
|
+
@channel = @bunny.create_channel
|
26
|
+
@exchange = @channel.exchange(@opts[:exchange], type: @opts[:exchange_type], durable: @opts[:durable])
|
27
|
+
end
|
28
|
+
|
29
|
+
def connected?
|
30
|
+
@bunny && @bunny.connected?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
|
2
|
+
class Sneakers::Queue
|
3
|
+
attr_reader :name, :opts, :exchange
|
4
|
+
|
5
|
+
def initialize(name, opts)
|
6
|
+
@name = name
|
7
|
+
@opts = opts
|
8
|
+
@handler_klass = Sneakers::CONFIG[:handler]
|
9
|
+
end
|
10
|
+
|
11
|
+
#
|
12
|
+
# :exchange
|
13
|
+
# :heartbeat_interval
|
14
|
+
# :prefetch
|
15
|
+
# :durable
|
16
|
+
# :ack
|
17
|
+
#
|
18
|
+
def subscribe(worker)
|
19
|
+
#@bunny = Bunny.new(@opts[:amqp], :vhost => @opts[:vhost], :heartbeat => @opts[:heartbeat], :logger => Sneakers::logger)
|
20
|
+
@bunny = @opts[:connection]
|
21
|
+
@bunny ||= Bunny.new(@opts[:amqp], :vhost => @opts[:vhost], :heartbeat => @opts[:heartbeat], :logger => Sneakers::logger)
|
22
|
+
@bunny.start
|
23
|
+
|
24
|
+
@channel = @bunny.create_channel
|
25
|
+
@channel.prefetch(@opts[:prefetch])
|
26
|
+
|
27
|
+
exchange_name = @opts[:exchange]
|
28
|
+
@exchange = @channel.exchange(exchange_name,
|
29
|
+
:type => @opts[:exchange_type],
|
30
|
+
:durable => @opts[:durable])
|
31
|
+
|
32
|
+
routing_key = @opts[:routing_key] || @name
|
33
|
+
routing_keys = [*routing_key]
|
34
|
+
|
35
|
+
# TODO: get the arguments from the handler? Retry handler wants this so you
|
36
|
+
# don't have to line up the queue's dead letter argument with the exchange
|
37
|
+
# you'll create for retry.
|
38
|
+
queue_durable = @opts[:queue_durable].nil? ? @opts[:durable] : @opts[:queue_durable]
|
39
|
+
queue = @channel.queue(@name, :durable => queue_durable, :arguments => @opts[:arguments])
|
40
|
+
|
41
|
+
if exchange_name.length > 0
|
42
|
+
routing_keys.each do |key|
|
43
|
+
queue.bind(@exchange, :routing_key => key)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# NOTE: we are using the worker's options. This is necessary so the handler
|
48
|
+
# has the same configuration as the worker. Also pass along the exchange and
|
49
|
+
# queue in case the handler requires access to them (for things like binding
|
50
|
+
# retry queues, etc).
|
51
|
+
handler_klass = worker.opts[:handler] || Sneakers::CONFIG.fetch(:handler)
|
52
|
+
handler = handler_klass.new(@channel, queue, worker.opts)
|
53
|
+
|
54
|
+
@consumer = queue.subscribe(:block => false, :manual_ack => @opts[:ack]) do | delivery_info, metadata, msg |
|
55
|
+
worker.do_work(delivery_info, metadata, msg, handler)
|
56
|
+
end
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def unsubscribe
|
61
|
+
# XXX can we cancel bunny and channel too?
|
62
|
+
@consumer.cancel if @consumer
|
63
|
+
@consumer = nil
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'serverengine'
|
2
|
+
require 'sneakers/workergroup'
|
3
|
+
|
4
|
+
module Sneakers
|
5
|
+
class Runner
|
6
|
+
def initialize(worker_classes, opts={})
|
7
|
+
@runnerconfig = RunnerConfig.new(worker_classes, opts)
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
@se = ServerEngine.create(nil, WorkerGroup) { @runnerconfig.reload_config! }
|
12
|
+
@se.run
|
13
|
+
end
|
14
|
+
|
15
|
+
def stop
|
16
|
+
@se.stop
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
class RunnerConfig
|
22
|
+
def method_missing(meth, *args, &block)
|
23
|
+
if %w{ before_fork after_fork }.include? meth.to_s
|
24
|
+
@conf[meth] = block
|
25
|
+
elsif %w{ workers start_worker_delay amqp }.include? meth.to_s
|
26
|
+
@conf[meth] = args.first
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(worker_classes, opts)
|
33
|
+
@worker_classes = worker_classes
|
34
|
+
@conf = opts
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_h
|
38
|
+
@conf
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def reload_config!
|
43
|
+
Sneakers.logger.warn("Loading runner configuration...")
|
44
|
+
config_file = Sneakers::CONFIG[:runner_config_file]
|
45
|
+
|
46
|
+
if config_file
|
47
|
+
begin
|
48
|
+
instance_eval(File.read(config_file), config_file)
|
49
|
+
Sneakers.logger.info("Loading config with file: #{config_file}")
|
50
|
+
rescue
|
51
|
+
Sneakers.logger.error("Cannot load from file '#{config_file}', #{$!}")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
config = make_serverengine_config
|
56
|
+
|
57
|
+
[:before_fork, :after_fork].each do | hook |
|
58
|
+
Sneakers::CONFIG[:hooks][hook] = config.delete(hook) if config[hook]
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
Sneakers.logger.info("New configuration: #{config.inspect}")
|
63
|
+
config
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def make_serverengine_config
|
68
|
+
# From Sneakers#setup_general_logger, there's support for a Logger object
|
69
|
+
# in CONFIG[:log]. However, serverengine takes an object in :logger.
|
70
|
+
# Pass our logger object so there's no issue about sometimes passing a
|
71
|
+
# file and sometimes an object.
|
72
|
+
without_log = Sneakers::CONFIG.merge(@conf)
|
73
|
+
without_log.delete(:log)
|
74
|
+
Sneakers::CONFIG.merge(@conf).merge({
|
75
|
+
:logger => Sneakers.logger,
|
76
|
+
:worker_type => 'process',
|
77
|
+
:worker_classes => @worker_classes
|
78
|
+
})
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|