creeper 1.0.9 → 2.0.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.
Files changed (145) hide show
  1. data/.gitignore +1 -0
  2. data/.rvmrc +48 -0
  3. data/Gemfile +17 -1
  4. data/Guardfile +32 -0
  5. data/Rakefile +9 -1
  6. data/bin/creeper +10 -58
  7. data/bin/creeperctl +74 -0
  8. data/config.ru +18 -0
  9. data/creeper.gemspec +19 -9
  10. data/lib/creeper.rb +108 -413
  11. data/lib/creeper/beanstalk_connection.rb +35 -0
  12. data/lib/creeper/cli.rb +225 -0
  13. data/lib/creeper/client.rb +93 -0
  14. data/lib/creeper/core_ext.rb +54 -0
  15. data/lib/creeper/exception_handler.rb +30 -0
  16. data/lib/creeper/extensions/action_mailer.rb +33 -0
  17. data/lib/creeper/extensions/active_record.rb +30 -0
  18. data/lib/creeper/extensions/generic_proxy.rb +26 -0
  19. data/lib/creeper/fetch.rb +94 -0
  20. data/lib/creeper/legacy.rb +46 -0
  21. data/lib/creeper/logging.rb +46 -0
  22. data/lib/creeper/manager.rb +164 -0
  23. data/lib/creeper/middleware/chain.rb +100 -0
  24. data/lib/creeper/middleware/server/active_record.rb +13 -0
  25. data/lib/creeper/middleware/server/logging.rb +31 -0
  26. data/lib/creeper/middleware/server/retry_jobs.rb +79 -0
  27. data/lib/creeper/middleware/server/timeout.rb +21 -0
  28. data/lib/creeper/paginator.rb +31 -0
  29. data/lib/creeper/processor.rb +116 -0
  30. data/lib/creeper/rails.rb +21 -0
  31. data/lib/creeper/redis_connection.rb +28 -0
  32. data/lib/creeper/testing.rb +44 -0
  33. data/lib/creeper/util.rb +45 -0
  34. data/lib/creeper/version.rb +1 -1
  35. data/lib/creeper/web.rb +248 -0
  36. data/lib/creeper/worker.rb +62 -313
  37. data/spec/dummy/.gitignore +15 -0
  38. data/spec/dummy/Gemfile +51 -0
  39. data/spec/dummy/README.rdoc +261 -0
  40. data/spec/dummy/Rakefile +7 -0
  41. data/spec/dummy/app/assets/images/rails.png +0 -0
  42. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  43. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  44. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  45. data/spec/dummy/app/controllers/work_controller.rb +71 -0
  46. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  47. data/spec/dummy/app/mailers/.gitkeep +0 -0
  48. data/spec/dummy/app/mailers/user_mailer.rb +9 -0
  49. data/spec/dummy/app/models/.gitkeep +0 -0
  50. data/spec/dummy/app/models/post.rb +8 -0
  51. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  52. data/spec/dummy/app/views/user_mailer/greetings.html.erb +3 -0
  53. data/spec/dummy/app/views/work/index.html.erb +1 -0
  54. data/spec/dummy/app/workers/fast_worker.rb +10 -0
  55. data/spec/dummy/app/workers/hard_worker.rb +11 -0
  56. data/spec/dummy/app/workers/lazy_worker.rb +12 -0
  57. data/spec/dummy/app/workers/suicidal_worker.rb +33 -0
  58. data/spec/dummy/config.ru +4 -0
  59. data/spec/dummy/config/application.rb +68 -0
  60. data/spec/dummy/config/boot.rb +6 -0
  61. data/spec/dummy/config/creeper.yml +9 -0
  62. data/spec/dummy/config/database.yml +25 -0
  63. data/spec/dummy/config/environment.rb +5 -0
  64. data/spec/dummy/config/environments/development.rb +37 -0
  65. data/spec/dummy/config/environments/production.rb +67 -0
  66. data/spec/dummy/config/environments/test.rb +37 -0
  67. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  68. data/spec/dummy/config/initializers/creeper.rb +8 -0
  69. data/spec/dummy/config/initializers/inflections.rb +15 -0
  70. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  71. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  72. data/spec/dummy/config/initializers/session_store.rb +8 -0
  73. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  74. data/spec/dummy/config/locales/en.yml +5 -0
  75. data/spec/dummy/config/routes.rb +13 -0
  76. data/spec/dummy/db/migrate/20120123214055_create_posts.rb +10 -0
  77. data/spec/dummy/db/schema.rb +23 -0
  78. data/spec/dummy/db/seeds.rb +7 -0
  79. data/spec/dummy/lib/assets/.gitkeep +0 -0
  80. data/spec/dummy/lib/tasks/.gitkeep +0 -0
  81. data/spec/dummy/log/.gitkeep +0 -0
  82. data/spec/dummy/public/404.html +26 -0
  83. data/spec/dummy/public/422.html +26 -0
  84. data/spec/dummy/public/500.html +25 -0
  85. data/spec/dummy/public/favicon.ico +0 -0
  86. data/spec/dummy/public/index.html +241 -0
  87. data/spec/dummy/public/robots.txt +5 -0
  88. data/spec/dummy/script/rails +6 -0
  89. data/spec/dummy/vendor/assets/javascripts/.gitkeep +0 -0
  90. data/spec/dummy/vendor/assets/stylesheets/.gitkeep +0 -0
  91. data/spec/dummy/vendor/plugins/.gitkeep +0 -0
  92. data/spec/lib/creeper/cli_spec.rb +208 -0
  93. data/spec/lib/creeper/client_spec.rb +110 -0
  94. data/spec/lib/creeper/exception_handler_spec.rb +110 -0
  95. data/spec/lib/creeper/processor_spec.rb +92 -0
  96. data/spec/lib/creeper/testing_spec.rb +105 -0
  97. data/spec/lib/creeper_spec.rb +54 -120
  98. data/spec/spec_helper.rb +81 -7
  99. data/spec/support/config.yml +9 -0
  100. data/spec/support/fake_env.rb +0 -0
  101. data/spec/support/workers/base_worker.rb +11 -0
  102. data/spec/support/workers/my_worker.rb +4 -0
  103. data/spec/support/workers/queued_worker.rb +5 -0
  104. data/spec/support/workers/real_worker.rb +10 -0
  105. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  106. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  107. data/web/assets/javascripts/application.js +49 -0
  108. data/web/assets/javascripts/vendor/bootstrap.js +12 -0
  109. data/web/assets/javascripts/vendor/bootstrap/bootstrap-alert.js +91 -0
  110. data/web/assets/javascripts/vendor/bootstrap/bootstrap-button.js +98 -0
  111. data/web/assets/javascripts/vendor/bootstrap/bootstrap-carousel.js +154 -0
  112. data/web/assets/javascripts/vendor/bootstrap/bootstrap-collapse.js +136 -0
  113. data/web/assets/javascripts/vendor/bootstrap/bootstrap-dropdown.js +92 -0
  114. data/web/assets/javascripts/vendor/bootstrap/bootstrap-modal.js +210 -0
  115. data/web/assets/javascripts/vendor/bootstrap/bootstrap-popover.js +95 -0
  116. data/web/assets/javascripts/vendor/bootstrap/bootstrap-scrollspy.js +125 -0
  117. data/web/assets/javascripts/vendor/bootstrap/bootstrap-tab.js +130 -0
  118. data/web/assets/javascripts/vendor/bootstrap/bootstrap-tooltip.js +270 -0
  119. data/web/assets/javascripts/vendor/bootstrap/bootstrap-transition.js +51 -0
  120. data/web/assets/javascripts/vendor/bootstrap/bootstrap-typeahead.js +271 -0
  121. data/web/assets/javascripts/vendor/jquery.js +9266 -0
  122. data/web/assets/javascripts/vendor/jquery.timeago.js +148 -0
  123. data/web/assets/stylesheets/application.css +6 -0
  124. data/web/assets/stylesheets/layout.css +26 -0
  125. data/web/assets/stylesheets/vendor/bootstrap-responsive.css +567 -0
  126. data/web/assets/stylesheets/vendor/bootstrap.css +3365 -0
  127. data/web/views/_paging.slim +15 -0
  128. data/web/views/_summary.slim +9 -0
  129. data/web/views/_workers.slim +14 -0
  130. data/web/views/index.slim +10 -0
  131. data/web/views/layout.slim +37 -0
  132. data/web/views/poll.slim +3 -0
  133. data/web/views/queue.slim +15 -0
  134. data/web/views/queues.slim +19 -0
  135. data/web/views/retries.slim +31 -0
  136. data/web/views/retry.slim +52 -0
  137. data/web/views/scheduled.slim +27 -0
  138. metadata +341 -23
  139. data/lib/creeper/celluloid_ext.rb +0 -42
  140. data/lib/creeper/creep.rb +0 -25
  141. data/lib/creeper/err_logger.rb +0 -37
  142. data/lib/creeper/launcher.rb +0 -44
  143. data/lib/creeper/out_logger.rb +0 -39
  144. data/spec/lib/creeper/session_spec.rb +0 -15
  145. data/spec/lib/creeper/worker_spec.rb +0 -21
@@ -0,0 +1,35 @@
1
+ require 'beanstalk-client'
2
+
3
+ module Creeper
4
+ class BeanstalkConnection
5
+ def self.create(options={})
6
+ client_options.merge!(options)
7
+
8
+ url = client_options[:url] || ENV['BEANSTALK_URL'] || 'beanstalk://127.0.0.1:11300/'
9
+ default = client_options[:default]
10
+ tubes = client_options[:tubes] || Creeper.job_descriptions.keys
11
+
12
+ build_client(url, default, tubes)
13
+ end
14
+
15
+ def self.build_client(urls, default, tubes)
16
+ uris = [*urls].flatten.map do |url_string|
17
+ url_string.split(/[\s,]+/).map do |url|
18
+ uri = URI.parse(url)
19
+ "#{uri.host}:#{uri.port || 11300}"
20
+ end
21
+ end.flatten
22
+
23
+ Beanstalk::Pool.new(uris, default).tap do |client|
24
+ tubes.each do |tube|
25
+ client.watch(tube)
26
+ end
27
+ end
28
+ end
29
+ private_class_method :build_client
30
+
31
+ def self.client_options
32
+ @client_options ||= {}
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,225 @@
1
+ trap 'INT' do
2
+ # Handle Ctrl-C in JRuby like MRI
3
+ # http://jira.codehaus.org/browse/JRUBY-4637
4
+ Creeper::CLI.instance.interrupt
5
+ end
6
+
7
+ trap 'TERM' do
8
+ # Heroku sends TERM and then waits 10 seconds for process to exit.
9
+ Creeper::CLI.instance.interrupt
10
+ end
11
+
12
+ trap 'QUIT' do
13
+ Creeper.logger.info "Received QUIT, no longer accepting new work"
14
+ mgr = Creeper::CLI.instance.manager
15
+ if mgr
16
+ mgr.stop!(shutdown: true, timeout: Creeper.options[:timeout])
17
+ mgr.wait(:shutdown)
18
+ # Explicitly exit so busy Processor threads can't block
19
+ # process shutdown.
20
+ exit(0)
21
+ end
22
+ end
23
+
24
+ trap 'USR1' do
25
+ Creeper.logger.info "Received USR1, no longer accepting new work"
26
+ mgr = Creeper::CLI.instance.manager
27
+ mgr.stop! if mgr
28
+ end
29
+
30
+ trap 'TTIN' do
31
+ Thread.list.each do |thread|
32
+ Creeper.logger.info "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
33
+ Creeper.logger.info thread.backtrace.join("\n")
34
+ end
35
+ end
36
+
37
+ $stdout.sync = true
38
+
39
+ require 'yaml'
40
+ require 'singleton'
41
+ require 'optparse'
42
+ require 'celluloid'
43
+
44
+ require 'creeper'
45
+ require 'creeper/util'
46
+ require 'creeper/manager'
47
+ # require 'creeper/scheduled'
48
+
49
+ module Creeper
50
+ class CLI
51
+ include Util
52
+ include Singleton
53
+
54
+ # Used for CLI testing
55
+ attr_accessor :code
56
+ attr_accessor :manager
57
+
58
+ def initialize
59
+ @code = nil
60
+ @interrupt_mutex = Mutex.new
61
+ @interrupted = false
62
+ end
63
+
64
+ def parse(args=ARGV)
65
+ @code = nil
66
+ Creeper.logger
67
+
68
+ cli = parse_options(args)
69
+ config = parse_config(cli)
70
+ options.merge!(config.merge(cli))
71
+
72
+ Creeper.logger.level = Logger::DEBUG if options[:verbose]
73
+ Celluloid.logger = nil unless options[:verbose]
74
+
75
+ validate!
76
+ write_pid
77
+ boot_system
78
+ end
79
+
80
+ def run
81
+ @manager = Creeper::Manager.new(options)
82
+ begin
83
+ logger.info 'Starting processing, hit Ctrl-C to stop'
84
+ @manager.start!
85
+ sleep
86
+ rescue Interrupt
87
+ logger.info 'Shutting down'
88
+ @manager.stop!(:shutdown => true, :timeout => options[:timeout])
89
+ @manager.wait(:shutdown)
90
+ # Explicitly exit so busy Processor threads can't block
91
+ # process shutdown.
92
+ exit(0)
93
+ end
94
+ end
95
+
96
+ def interrupt
97
+ @interrupt_mutex.synchronize do
98
+ unless @interrupted
99
+ @interrupted = true
100
+ Thread.main.raise Interrupt
101
+ end
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ def die(code)
108
+ exit(code)
109
+ end
110
+
111
+ def options
112
+ Creeper.options
113
+ end
114
+
115
+ def detected_environment
116
+ options[:environment] ||= ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
117
+ end
118
+
119
+ def boot_system
120
+ ENV['RACK_ENV'] = ENV['RAILS_ENV'] = detected_environment
121
+
122
+ raise ArgumentError, "#{options[:require]} does not exist" unless File.exist?(options[:require])
123
+
124
+ if File.directory?(options[:require])
125
+ require 'rails'
126
+ require 'creeper/rails'
127
+ require File.expand_path("#{options[:require]}/config/environment.rb")
128
+ ::Rails.application.eager_load!
129
+ else
130
+ require options[:require]
131
+ end
132
+ end
133
+
134
+ def validate!
135
+ options[:queues] << 'default' if options[:queues].empty?
136
+
137
+ if !File.exist?(options[:require]) ||
138
+ (File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
139
+ logger.info "=================================================================="
140
+ logger.info " Please point creeper to a Rails 3 application or a Ruby file "
141
+ logger.info " to load your worker classes with -r [DIR|FILE]."
142
+ logger.info "=================================================================="
143
+ logger.info @parser
144
+ die(1)
145
+ end
146
+ end
147
+
148
+ def parse_options(argv)
149
+ opts = {}
150
+
151
+ @parser = OptionParser.new do |o|
152
+ o.on "-q", "--queue QUEUE[,WEIGHT]...", "Queues to process with optional weights" do |arg|
153
+ queues_and_weights = arg.scan(/([\w-]+),?(\d*)/)
154
+ queues_and_weights.each {|queue_and_weight| parse_queues(opts, *queue_and_weight)}
155
+ opts[:strict] = queues_and_weights.collect(&:last).none? {|weight| weight != ''}
156
+ end
157
+
158
+ o.on "-v", "--verbose", "Print more verbose output" do
159
+ Creeper.logger.level = ::Logger::DEBUG
160
+ end
161
+
162
+ o.on '-e', '--environment ENV', "Application environment" do |arg|
163
+ opts[:environment] = arg
164
+ end
165
+
166
+ o.on '-t', '--timeout NUM', "Shutdown timeout" do |arg|
167
+ opts[:timeout] = arg.to_i
168
+ end
169
+
170
+ o.on '-r', '--require [PATH|DIR]', "Location of Rails application with workers or file to require" do |arg|
171
+ opts[:require] = arg
172
+ end
173
+
174
+ o.on '-c', '--concurrency INT', "processor threads to use" do |arg|
175
+ opts[:concurrency] = arg.to_i
176
+ end
177
+
178
+ o.on '-P', '--pidfile PATH', "path to pidfile" do |arg|
179
+ opts[:pidfile] = arg
180
+ end
181
+
182
+ o.on '-C', '--config PATH', "path to YAML config file" do |arg|
183
+ opts[:config_file] = arg
184
+ end
185
+
186
+ o.on '-V', '--version', "Print version and exit" do |arg|
187
+ puts "Creeper #{Creeper::VERSION}"
188
+ die(0)
189
+ end
190
+ end
191
+
192
+ @parser.banner = "creeper [options]"
193
+ @parser.on_tail "-h", "--help", "Show help" do
194
+ logger.info @parser
195
+ die 1
196
+ end
197
+ @parser.parse!(argv)
198
+ opts
199
+ end
200
+
201
+ def write_pid
202
+ if path = options[:pidfile]
203
+ File.open(path, 'w') do |f|
204
+ f.puts Process.pid
205
+ end
206
+ end
207
+ end
208
+
209
+ def parse_config(cli)
210
+ opts = {}
211
+ if cli[:config_file] && File.exist?(cli[:config_file])
212
+ opts = YAML.load_file cli[:config_file]
213
+ queues = opts.delete(:queues) || []
214
+ queues.each { |name, weight| parse_queues(opts, name, weight) }
215
+ end
216
+ opts
217
+ end
218
+
219
+ def parse_queues(opts, q, weight)
220
+ [weight.to_i, 1].max.times do
221
+ (opts[:queues] ||= []) << q
222
+ end
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,93 @@
1
+ require 'creeper/legacy'
2
+ require 'creeper/middleware/chain'
3
+
4
+ module Creeper
5
+ class Client
6
+
7
+ def self.default_middleware
8
+ Middleware::Chain.new do |m|
9
+ end
10
+ end
11
+
12
+ def self.registered_workers
13
+ Creeper.redis { |x| x.smembers('workers') }
14
+ end
15
+
16
+ def self.registered_queues
17
+ Creeper.redis { |x| x.smembers('queues') }
18
+ end
19
+
20
+ ##
21
+ # The main method used to push a job to Redis. Accepts a number of options:
22
+ #
23
+ # queue - the named queue to use, default 'default'
24
+ # class - the worker class to call, required
25
+ # args - an array of simple arguments to the perform method, must be JSON-serializable
26
+ # retry - whether to retry this job if it fails, true or false, default true
27
+ # backtrace - whether to save any error backtrace, default false
28
+ #
29
+ # All options must be strings, not symbols. NB: because we are serializing to JSON, all
30
+ # symbols in 'args' will be converted to strings.
31
+ #
32
+ # Returns nil if not pushed to Redis or a unique Job ID if pushed.
33
+ #
34
+ # Example:
35
+ # Creeper::Client.push('queue' => 'my_queue', 'class' => MyWorker, 'args' => ['foo', 1, :bat => 'bar'])
36
+ #
37
+ def self.push(item)
38
+ raise(ArgumentError, "Message must be a Hash of the form: { 'class' => SomeWorker, 'args' => ['bob', 1, :foo => 'bar'] }") unless item.is_a?(Hash)
39
+ raise(ArgumentError, "Message must include a class and set of arguments: #{item.inspect}") if !item['class'] || !item['args']
40
+ raise(ArgumentError, "Message must include a Creeper::Worker class, not class name: #{item['class'].ancestors.inspect}") if !item['class'].is_a?(Class) || !item['class'].respond_to?('get_creeper_options')
41
+
42
+ worker_class = item['class']
43
+ item['class'] = item['class'].to_s
44
+
45
+ item = worker_class.get_creeper_options.merge(item)
46
+ # item['retry'] = !!item['retry']
47
+ at = item['at']
48
+ queue = item['queue']
49
+ priority = item['priority']
50
+ delay = item['delay']
51
+ delay ||= at.to_i - Time.now.to_i if at and at.respond_to?(:to_i)
52
+ time_to_run = item['time_to_run']
53
+ # item['jid'] = SecureRandom.base64
54
+
55
+ pushed = false
56
+ job = Creeper.client_middleware.invoke(worker_class, item, queue) do
57
+ payload = Creeper.dump_json([ queue, item ])
58
+ args = [ payload, priority, delay, time_to_run ]
59
+ args.pop while args.last.nil?
60
+ Creeper.redis do |conn|
61
+ conn.sadd('queues', queue)
62
+ end
63
+ Creeper.beanstalk do |beanstalk|
64
+ beanstalk.on_tube(queue) do |conn|
65
+ conn.put(*args)
66
+ end
67
+ end
68
+ # Creeper.redis do |conn|
69
+ # if item['at']
70
+ # pushed = conn.zadd('schedule', item['at'].to_s, payload)
71
+ # else
72
+ # _, pushed = conn.multi do
73
+ # conn.sadd('queues', queue)
74
+ # conn.rpush("queue:#{queue}", payload)
75
+ # end
76
+ # end
77
+ # end
78
+ end
79
+ pushed = !!job
80
+ pushed ? job : nil
81
+ end
82
+
83
+ # Redis compatibility helper. Example usage:
84
+ #
85
+ # Creeper::Client.enqueue(MyWorker, 'foo', 1, :bat => 'bar')
86
+ #
87
+ # Messages are enqueued to the 'default' queue.
88
+ #
89
+ def self.enqueue(klass, *args)
90
+ klass.perform_async(*args)
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,54 @@
1
+ begin
2
+ require 'active_support/core_ext/class/attribute'
3
+ rescue LoadError
4
+
5
+ # A dumbed down version of ActiveSupport's
6
+ # Class#class_attribute helper.
7
+ class Class
8
+ def class_attribute(*attrs)
9
+ instance_reader = true
10
+ instance_writer = true
11
+
12
+ attrs.each do |name|
13
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
14
+ def self.#{name}() nil end
15
+ def self.#{name}?() !!#{name} end
16
+
17
+ def self.#{name}=(val)
18
+ singleton_class.class_eval do
19
+ define_method(:#{name}) { val }
20
+ end
21
+
22
+ if singleton_class?
23
+ class_eval do
24
+ def #{name}
25
+ defined?(@#{name}) ? @#{name} : singleton_class.#{name}
26
+ end
27
+ end
28
+ end
29
+ val
30
+ end
31
+
32
+ if instance_reader
33
+ def #{name}
34
+ defined?(@#{name}) ? @#{name} : self.class.#{name}
35
+ end
36
+
37
+ def #{name}?
38
+ !!#{name}
39
+ end
40
+ end
41
+ RUBY
42
+
43
+ attr_writer name if instance_writer
44
+ end
45
+ end
46
+
47
+ private
48
+ def singleton_class?
49
+ ancestors.first != self
50
+ end
51
+ end
52
+ end
53
+
54
+
@@ -0,0 +1,30 @@
1
+ module Creeper
2
+ module ExceptionHandler
3
+
4
+ def handle_exception(ex, msg)
5
+ Creeper.logger.warn msg
6
+ Creeper.logger.warn ex
7
+ Creeper.logger.warn ex.backtrace.join("\n")
8
+ send_to_airbrake(msg, ex) if defined?(::Airbrake)
9
+ send_to_exceptional(msg, ex) if defined?(::Exceptional)
10
+ send_to_exception_notifier(msg, ex) if defined?(::ExceptionNotifier)
11
+ end
12
+
13
+ private
14
+
15
+ def send_to_airbrake(msg, ex)
16
+ ::Airbrake.notify(ex, :parameters => msg)
17
+ end
18
+
19
+ def send_to_exceptional(msg, ex)
20
+ if ::Exceptional::Config.should_send_to_api?
21
+ ::Exceptional.context(msg)
22
+ ::Exceptional::Remote.error(::Exceptional::ExceptionData.new(ex))
23
+ end
24
+ end
25
+
26
+ def send_to_exception_notifier(msg, ex)
27
+ ::ExceptionNotifier::Notifier.background_exception_notification(ex, :data => { :message => msg }).deliver
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ require 'creeper/extensions/generic_proxy'
2
+
3
+ module Creeper
4
+ module Extensions
5
+ ##
6
+ # Adds 'delay' and 'delay_for' to ActionMailer to offload arbitrary email
7
+ # delivery to Creeper. Example:
8
+ #
9
+ # UserMailer.delay.send_welcome_email(new_user)
10
+ # UserMailer.delay_for(5.days).send_welcome_email(new_user)
11
+ class DelayedMailer
12
+ include Creeper::Worker
13
+ # I think it's reasonable to assume that emails should take less
14
+ # than 30 seconds to send.
15
+ creeper_options :timeout => 30
16
+
17
+ def perform(yml)
18
+ (target, method_name, args) = YAML.load(yml)
19
+ target.send(method_name, *args).deliver
20
+ end
21
+ end
22
+
23
+ module ActionMailer
24
+ def delay
25
+ Proxy.new(DelayedMailer, self)
26
+ end
27
+ def delay_for(interval)
28
+ Proxy.new(DelayedMailer, self, Time.now.to_f + interval.to_f)
29
+ end
30
+ end
31
+
32
+ end
33
+ end