delayed_job_tracer 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
@@ -0,0 +1,111 @@
1
+ delayed_job_tracer -- Like a tracer bullet for your delayed_job queue.
2
+ ====================================
3
+
4
+ ## DESCRIPTION
5
+
6
+ The delayed_job_tracer gem is designed to monitor the delayed_job gem ([https://github.com/collectiveidea/delayed_job](https://github.com/collectiveidea/delayed_job)). It will alert you via e-mail if something goes wrong, such as if the delayed_job process crashes or one of its jobs fails or takes too long to complete.
7
+
8
+ This gem also detects wither e-mails sent through delayed_job are actually being delivered by sending test messages (like tracer bullets) to an e-mail account then checking that account to ensure they were processed and dispatched via the delayed_job queue. The e-mail delivery detection allows you to be notified if e-mails can't be sent out either because of delayed_job having choked or because of an issue with the e-mail account the application uses to send e-mail.
9
+
10
+
11
+ ## Primary Features
12
+
13
+ * Sends an e-mail notification when delayed_job crashes or is unable to complete a job in a reasonable amount of time.
14
+ * Sends an e-mail notification when the Rails application is unable to successfully deliver e-mails via delayed_job.
15
+ * Circumvents the need for a process monitoring tool like Monit or God for delayed_job processes.
16
+ * Keeps memory usage low by working outside of Rails.
17
+
18
+
19
+ ## Rails and delayed_job support
20
+
21
+ * The master branch and all 1.x series gems work with delayed_job 2.1.x and above (Rails 3.x)
22
+ * The 2-3-stable branch and all 0.9.x series gems work with delayed_job 2.0.7 and above (Rails 2.3)
23
+
24
+
25
+ ## Why use a Cron'd ruby process to monitor delayed_job and e-mail delivery?
26
+
27
+ * Cron is often considered to be more reliable than process monitoring tools like Monit or God.
28
+ * The Cron'd Ruby process only uses a modest amount of memory (~27MB) for a few seconds at regular intervals, whereas a rake task or runner would equate to the entire size of the Rails application, with a memory footprint that grows over time.
29
+
30
+
31
+ ## Why use a Perl Script to send alert e-mails to admins?
32
+
33
+ * It's fast
34
+ * It doesn't rely on the Rails application
35
+ * It doesn't rely on the delayed_job process you're monitoring with this gem
36
+
37
+
38
+ ## Prerequisites
39
+
40
+ 1) Install XML and SSL Packages:
41
+
42
+ Note: These are for Ubuntu. You may need to find alternate packages for your platform.
43
+
44
+ sudo apt-get install libxml2-dev libxslt-dev libnet-ssleay-perl libcrypt-ssleay-perl libio-socket-ssl-perl
45
+
46
+ 2) sendEmail Perl script:
47
+
48
+ Download from "http://caspian.dotconf.net/menu/Software/SendEmail":http://caspian.dotconf.net/menu/Software/SendEmail and follow the setup instructions, or just follow these instructions:
49
+
50
+ wget -c http://caspian.dotconf.net/menu/Software/SendEmail/sendEmail-v1.56.tar.gz
51
+ tar xvfz sendEmail-v1.56.tar.gz
52
+ sudo cp -a sendEmail-v1.56/sendEmail /usr/local/bin
53
+ chmod +x /usr/local/bin/sendEmail
54
+
55
+ Run it to be sure it works:
56
+
57
+ sendEmail
58
+
59
+ Cleanup:
60
+
61
+ rm -rf sendEmail-v1.56*
62
+
63
+
64
+ ## Installation
65
+
66
+ 1) Add the gem to your Gemfile:
67
+
68
+ gem "delayed_job_tracer"
69
+
70
+ 2) Generate the config file:
71
+
72
+ rails g delayed_job_tracer
73
+
74
+ ## Configuration
75
+
76
+ 1) Create a 'DelayedJobTracer' folder/label in the e-mail account you configure the plugin to send test messages to and a filter to move all incoming messages with '[DelayedJobTracer]' in the subject line to this folder (or label if you're using Gmail). Set the name of this folder in the config/delayed_job_tracer_config.yml file - as it will be the folder that the Ruby process checks for test messages.
77
+
78
+ 2) Add a Cron job on the server:
79
+
80
+ Example for running every 15 minutes, replace with your actual app and ruby locations:
81
+
82
+ */15 * * * * /usr/local/bin/delayed_job_tracer -c /opt/apps/appname/current/config/delayed_job_tracer_config.yml
83
+
84
+ Note: If you're using bundler to install gems within an application-specific gem environment, you may need to also install the gem manually on the server so that the executable is available via the location in the example cron entry above.
85
+
86
+ Finish configuring your settings in the config/delayed_job_tracer_config.yml file. Your database and ActionMailer config will be applied via the generator, but you'll still need to provide an alternate e-mail account to send administrative notifications through, provide the admin's e-mail address and so forth.
87
+
88
+
89
+ ## License
90
+
91
+ The MIT License
92
+
93
+ Copyright (c) 2010-2011 Kenny Johnston
94
+
95
+ Permission is hereby granted, free of charge, to any person obtaining a copy
96
+ of this software and associated documentation files (the "Software"), to deal
97
+ in the Software without restriction, including without limitation the rights
98
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99
+ copies of the Software, and to permit persons to whom the Software is
100
+ furnished to do so, subject to the following conditions:
101
+
102
+ The above copyright notice and this permission notice shall be included in
103
+ all copies or substantial portions of the Software.
104
+
105
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
106
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
107
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
108
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
109
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
110
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
111
+ THE SOFTWARE.
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'delayed_job_tracer'
4
+ require 'optparse'
5
+ require 'yaml'
6
+
7
+ options = {}
8
+
9
+ optparse = OptionParser.new do |opts|
10
+ # Displayed at top of help screen
11
+ opts.banner = "Usage: delayed_job_tracer -c /opt/apps/myapp/current/config/delayed_job_tracer_config.yml"
12
+
13
+ opts.on( '-c', '--config FILE', 'Path to config file' ) do |file|
14
+ options[:config] = file
15
+ end
16
+
17
+ opts.on( '-h', '--help', 'Display this screen' ) do
18
+ puts opts
19
+ exit
20
+ end
21
+ end
22
+
23
+ # Parse the command-line. Remember there are two forms
24
+ # of the parse method. The 'parse' method simply parses
25
+ # ARGV, while the 'parse!' method parses ARGV and removes
26
+ # any options found there, as well as any parameters for
27
+ # the options. What's left is the list of files to resize.
28
+ optparse.parse!
29
+
30
+ begin
31
+ raise "no config file provided" unless options[:config]
32
+ raise "no config file found at '#{options[:config]}'" unless File.exists?(options[:config])
33
+ config = YAML.load_file(options[:config])
34
+ raise "incorrect config file: '#{options[:config]}'" unless config['app']
35
+ DelayedJobTracer.run(config)
36
+ rescue Exception => e
37
+ puts "Error: #{e}"
38
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "delayed_job_tracer/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "delayed_job_tracer"
7
+ s.version = DelayedJobTracer::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Kenny Johnston"]
10
+ s.email = ["kjohnston.ca@gmail.com"]
11
+ s.homepage = "https://github.com/kjohnston/delayed_job_tracer"
12
+ s.summary = %q{Like a tracer bullet for your delayed_job queue}
13
+ s.description = %q{Monitors the delayed_job queue and periodically tests its ability to deliver
14
+ e-mail messages and e-mails you if something goes wrong, such as the delayed_job
15
+ process crashes or one of its jobs fails or takes too long to complete.}
16
+
17
+ s.add_runtime_dependency "delayed_job", "~> 2.0.7"
18
+ s.add_runtime_dependency "mms2r", "2.4.1"
19
+ s.add_runtime_dependency "mysql2", ">= 0.2.6", "<= 0.4"
20
+ s.add_runtime_dependency "tmail", "1.2.7.1"
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.require_paths = ["lib"]
26
+ end
@@ -0,0 +1,25 @@
1
+ class DelayedJobTracerGenerator < Rails::Generator::Base
2
+
3
+ attr_accessor :app_name, :db_host, :db_database, :db_username, :db_password,
4
+ :mail_domain, :mail_username, :mail_password
5
+
6
+ def manifest
7
+ @app_name = Rails.root.to_s.split('/').last
8
+
9
+ db_config = Rails::Configuration.new
10
+ @db_host = db_config.database_configuration["production"]["host"]
11
+ @db_database = db_config.database_configuration["production"]["database"]
12
+ @db_username = db_config.database_configuration["production"]["username"]
13
+ @db_password = db_config.database_configuration["production"]["password"]
14
+
15
+ mail_config = ActionMailer::Base.smtp_settings
16
+ @mail_domain = mail_config[:address]
17
+ @mail_username = mail_config[:user_name]
18
+ @mail_password = mail_config[:password]
19
+
20
+ record do |m|
21
+ m.template 'delayed_job_tracer_config.yml', File.join('config', 'delayed_job_tracer_config.yml')
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,55 @@
1
+ # Application's name
2
+ # This will be the prefix of alert e-mail subject lines, which will have brackets
3
+ # added around it.
4
+ # Example: App Name => [App Name]
5
+ app:
6
+ name: <%= app_name %>
7
+
8
+
9
+ # Administrative e-mail address to send alerts to
10
+ admin:
11
+ email:
12
+
13
+
14
+ # Number of seconds after a delayed_job is considered stale.
15
+ # An e-mail will be sent to the admin if records older than this are found
16
+ # in the delayed_jobs table. This will catch 3 scenarios:
17
+ # 1) The delayed_job process locked up or crashed and jobs stopeed running
18
+ # 2) One or more jobs failed and could not succeed on retry
19
+ # 3) One or more jobs are taking longer than the number of seconds set here
20
+ # to complete
21
+ delayed_job:
22
+ stale: 3600
23
+
24
+
25
+ # Application's production database
26
+ database:
27
+ ip: <%= db_host %>
28
+ database: <%= db_database %>
29
+ user: <%= db_username %>
30
+ password: <%= db_password %>
31
+
32
+
33
+ # E-mail account to monitor for test messages
34
+ # This account will be checked periodically via a cron'd execution of runner.rb to
35
+ # ensure that a recent monitoring message has been delivered sucessfully to that
36
+ # e-mail account.
37
+ monitor:
38
+ server: <%= mail_domain %>
39
+ username: <%= mail_username %>
40
+ password: <%= mail_password %>
41
+ port: 993
42
+ ssl: true
43
+ folder: DelayedJobTracer
44
+
45
+
46
+ # E-mail account to send admin alerts through via a perl script.
47
+ # You should use a different account than the applicaiton uses, so alert messages can
48
+ # still be sent if for example the Gmail send limit is exceeded for the application's
49
+ # e-mail account and Google puts it on lockdown.
50
+ alert:
51
+ server: smtp.gmail.com
52
+ username:
53
+ password:
54
+ port: 25
55
+ tls: true
@@ -0,0 +1,26 @@
1
+ if defined?(ActionMailer)
2
+ require File.dirname(__FILE__) + '/delayed_job_tracer/delayed_job_tracer_mailer.rb'
3
+ else
4
+ require File.dirname(__FILE__) + '/delayed_job_tracer/notifier'
5
+ require File.dirname(__FILE__) + '/delayed_job_tracer/message_finder'
6
+ require File.dirname(__FILE__) + '/delayed_job_tracer/mysql_interface'
7
+
8
+ class DelayedJobTracer
9
+
10
+ def self.config
11
+ @@config
12
+ end
13
+
14
+ def self.config=(c)
15
+ @@config = c
16
+ end
17
+
18
+ def self.run(config)
19
+ @@config = config
20
+ Notifier.notify_admin_of_email_issue unless MessageFinder.found_recent_message?
21
+ Notifier.notify_admin_of_queue_issue unless MySQLInterface.delayed_job_queue_ok?
22
+ MySQLInterface.queue_delayed_job
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ class DelayedJobTracerMailer < ActionMailer::Base
2
+
3
+ def delayed_job_tracer_test_message
4
+ config = YAML.load_file(Rails.root.join('config', 'delayed_job_tracer_config.yml'))
5
+ @from = ActionMailer::Base.smtp_settings[:user_name]
6
+ @recipients = config["monitor"]["username"]
7
+ @subject = "[DelayedJobTracer] #{Time.zone.now}"
8
+ @sent_on = Time.zone.now
9
+ @body = "This is a test message to ensure messages are being delivered successfully via delayed_job."
10
+ end
11
+
12
+ def self.enqueue_delayed_job_tracer_test_message
13
+ delay.deliver_delayed_job_tracer_test_message
14
+ end
15
+
16
+ end
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'net/imap'
3
+ require 'tmail'
4
+ require 'mms2r'
5
+ require 'yaml'
6
+
7
+ class MessageFinder
8
+
9
+ def self.found_recent_message?
10
+ c = DelayedJobTracer.config['monitor']
11
+
12
+ server = c['server']
13
+ port = c['port'] || 143
14
+ ssl = c['ssl'] || false
15
+ username = c['username']
16
+ password = c['password']
17
+ folder = c['folder']
18
+
19
+ imap = Net::IMAP.new server, port, ssl
20
+ imap.login username, password
21
+ imap.select folder
22
+ uids = imap.search(["UNSEEN"])
23
+
24
+ emails = []
25
+ times = []
26
+
27
+ uids.each do |uid|
28
+ mdata = imap.fetch(uid, 'RFC822')[0].attr['RFC822']
29
+ tmail = TMail::Mail.parse mdata
30
+ mms = MMS2R::Media.new tmail
31
+ emails << {:mdata => mdata, :tmail => tmail, :mms => mms, :uid => uid}
32
+ end
33
+
34
+ emails.each do |email|
35
+ # Collects the dates of unread messages
36
+ times << email[:tmail].date
37
+ # Marks unread messages as read
38
+ imap.store email[:uid], "+FLAGS", [:Seen]
39
+ end
40
+
41
+ imap.disconnect
42
+
43
+ # Delete tmp files used by mms2r
44
+ emails.map{|m| m[:mms].purge }
45
+
46
+ # Just checks to see if there was at least one recent unread message
47
+ return true unless times.blank?
48
+ end
49
+
50
+ end
@@ -0,0 +1,52 @@
1
+ require 'mysql2'
2
+
3
+ class MySQLInterface
4
+
5
+ # Connects to the db and submits a query
6
+ def self.query(sql)
7
+ c = DelayedJobTracer.config['database']
8
+ d = Mysql2::Client.new(:host => c['ip'], :database => c['database'], :username => c['user'], :password => c['password'])
9
+ d.query(sql)
10
+ end
11
+
12
+ # Returns true if there are no stale records
13
+ def self.delayed_job_queue_ok?
14
+ query(delayed_job_stale_records).count.zero?
15
+ end
16
+
17
+ # Inserts a delayed_job record
18
+ def self.queue_delayed_job
19
+ query(delayed_job_record).to_s
20
+ end
21
+
22
+ # SQL for selecting stale delayed_job records
23
+ def self.delayed_job_stale_records
24
+ c = DelayedJobTracer.config['delayed_job']
25
+ "SELECT * FROM delayed_jobs WHERE created_at < '#{(Time.now-c['stale']).utc.strftime("%Y-%m-%d %H:%M:%S")}'"
26
+ end
27
+
28
+ # SQL for inserting a delayed_job record
29
+ def self.delayed_job_record
30
+ "INSERT INTO delayed_jobs (`handler`, `run_at`, `created_at`, `updated_at`) VALUES
31
+ ('#{delayed_job_handler}', '#{mysql_timestamp}', '#{mysql_timestamp}', '#{mysql_timestamp}')"
32
+ end
33
+
34
+ # SQL helper method for inserting a delayed_job record
35
+ def self.delayed_job_handler
36
+ "--- !ruby/struct:Delayed::PerformableMethod
37
+ object: LOAD;DelayedJobTracerMailer
38
+ method: :deliver_delayed_job_tracer_test_message
39
+ args: []"
40
+ end
41
+
42
+ # Timestamp in the format that e-mails expect
43
+ def self.email_timestamp
44
+ Time.now.strftime("%a, %e %b %Y %H:%M:%S %z")
45
+ end
46
+
47
+ # Timestamp in the format that MySQL expects
48
+ def self.mysql_timestamp
49
+ Time.now.utc.strftime("%Y-%m-%d %H:%M:%S")
50
+ end
51
+
52
+ end
@@ -0,0 +1,29 @@
1
+ require 'yaml'
2
+
3
+ class Notifier
4
+
5
+ def self.notify_admin_of_email_issue
6
+ subject_suffix = 'E-mail Issue'
7
+ message_suffix = 'is having trouble sending e-mail via delayed_job, please investigate.'
8
+ send_notification(subject_suffix, message_suffix)
9
+ end
10
+
11
+ def self.notify_admin_of_queue_issue
12
+ subject_suffix = 'Queue Issue'
13
+ message_suffix = 'has one or more stale delayed_jobs, please investigate.'
14
+ send_notification(subject_suffix, message_suffix)
15
+ end
16
+
17
+ def self.send_notification(subject_suffix, message_suffix)
18
+ c = DelayedJobTracer.config
19
+ account = c['alert']
20
+ recipient = c['admin']['email']
21
+ appname = c['app']['name']
22
+ subject = "[#{appname}] DelayedJobTracer: " + subject_suffix
23
+ message = "#{appname} " + message_suffix
24
+ system "/usr/local/bin/sendEmail -f #{account['username']} -t #{recipient} -u '#{subject}' -m #{message} \
25
+ -s #{account['server']}:#{account['port']} -xu #{account['username']} -xp #{account['password']} \
26
+ #{ '-o tls=yes' if c['alert']['tls'] == 'true' }"
27
+ end
28
+
29
+ end
@@ -0,0 +1,3 @@
1
+ module DelayedJobTracer
2
+ VERSION = "0.9.0"
3
+ end
@@ -0,0 +1 @@
1
+ require "delayed_job_tracer"
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: delayed_job_tracer
3
+ version: !ruby/object:Gem::Version
4
+ hash: 59
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 9
9
+ - 0
10
+ version: 0.9.0
11
+ platform: ruby
12
+ authors:
13
+ - Kenny Johnston
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-09-13 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: delayed_job
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 1
29
+ segments:
30
+ - 2
31
+ - 0
32
+ - 7
33
+ version: 2.0.7
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: mms2r
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - "="
43
+ - !ruby/object:Gem::Version
44
+ hash: 29
45
+ segments:
46
+ - 2
47
+ - 4
48
+ - 1
49
+ version: 2.4.1
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: mysql2
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 27
61
+ segments:
62
+ - 0
63
+ - 2
64
+ - 6
65
+ version: 0.2.6
66
+ - - <=
67
+ - !ruby/object:Gem::Version
68
+ hash: 3
69
+ segments:
70
+ - 0
71
+ - 4
72
+ version: "0.4"
73
+ type: :runtime
74
+ version_requirements: *id003
75
+ - !ruby/object:Gem::Dependency
76
+ name: tmail
77
+ prerelease: false
78
+ requirement: &id004 !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - "="
82
+ - !ruby/object:Gem::Version
83
+ hash: 81
84
+ segments:
85
+ - 1
86
+ - 2
87
+ - 7
88
+ - 1
89
+ version: 1.2.7.1
90
+ type: :runtime
91
+ version_requirements: *id004
92
+ description: |-
93
+ Monitors the delayed_job queue and periodically tests its ability to deliver
94
+ e-mail messages and e-mails you if something goes wrong, such as the delayed_job
95
+ process crashes or one of its jobs fails or takes too long to complete.
96
+ email:
97
+ - kjohnston.ca@gmail.com
98
+ executables:
99
+ - delayed_job_tracer
100
+ extensions: []
101
+
102
+ extra_rdoc_files: []
103
+
104
+ files:
105
+ - .gitignore
106
+ - Gemfile
107
+ - README.md
108
+ - Rakefile
109
+ - bin/delayed_job_tracer
110
+ - delayed_job_tracer.gemspec
111
+ - generators/delayed_job_tracer/delayed_job_tracer_generator.rb
112
+ - generators/delayed_job_tracer/templates/delayed_job_tracer_config.yml
113
+ - lib/delayed_job_tracer.rb
114
+ - lib/delayed_job_tracer/delayed_job_tracer_mailer.rb
115
+ - lib/delayed_job_tracer/message_finder.rb
116
+ - lib/delayed_job_tracer/mysql_interface.rb
117
+ - lib/delayed_job_tracer/notifier.rb
118
+ - lib/delayed_job_tracer/version.rb
119
+ - rails/init.rb
120
+ homepage: https://github.com/kjohnston/delayed_job_tracer
121
+ licenses: []
122
+
123
+ post_install_message:
124
+ rdoc_options: []
125
+
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ hash: 3
134
+ segments:
135
+ - 0
136
+ version: "0"
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ hash: 3
143
+ segments:
144
+ - 0
145
+ version: "0"
146
+ requirements: []
147
+
148
+ rubyforge_project:
149
+ rubygems_version: 1.8.6
150
+ signing_key:
151
+ specification_version: 3
152
+ summary: Like a tracer bullet for your delayed_job queue
153
+ test_files: []
154
+