diff --git a/README.md b/README.md index 2db4cb2..f0dba5d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # Resque::Integration -Интеграция Resque в Rails-приложения с поддержкой плагинов [resque-progress](https://github.com/idris/resque-progress), [resque-lock](https://github.com/defunkt/resque-lock) и [resque-multi-job-forks](https://github.com/stulentsev/resque-multi-job-forks). +Интеграция Resque в Rails-приложения с поддержкой следующих плагинов: +* [resque-progress](https://github.com/idris/resque-progress) +* [resque-lock](https://github.com/defunkt/resque-lock) +* [resque-multi-job-forks](https://github.com/stulentsev/resque-multi-job-forks) +* [resque-failed-job-mailer](https://github.com/anandagrawal84/resque_failed_job_mailer) + Этот гем существует затем, чтобы избежать повторения чужих ошибок и сократить время, необходимое для включения resque в проект. ## Установка @@ -17,13 +22,6 @@ mount Resque::Integration::Application => "/_job_", :as => "job_status" Вместо `_job_` можно прописать любой другой адрес. По этому адресу прогресс-бар будет узнавать о состоянии джоба. -Создайте `config/initializers/resque.rb`: -```ruby -Resque.redis = $redis -Resque.inline = Rails.env.test? -Resque.redis.namespace = "your_project_resque" -``` - Если вы до сих пор не используете sprockets, то сделайте что-то вроде этого: ```bash $ rails generate resque:integration:install @@ -119,6 +117,23 @@ workers: minutes_per_fork: 30 # альтернатива предыдущей настройке - сколько минут должен работать воркер, прежде чем форкнуться заново env: # переменные окружение, специфичные для данного воркера RUBY_HEAP_SLOTS_GROWTH_FACTOR: 0.5 + +# конфигурация failure-бэкэндов +failure: + # конфигурация отправщика отчетов об ошибках + notifier: + enabled: true + # адреса, на которые надо посылать уведомления об ошибках + to: [teamlead@apress.ru, pm@apress.ru, programmer@apress.ru] + # необязательные настройки + # от какого адреса слать + from: no-reply@blizko.ru + # включать в письмо payload (аргументы, с которыми вызвана задача) + include_payload: true + # класс отправщика (должен быть наследником ActionMailer::Base, по умолчанию ResqueFailedJobMailer::Mailer + mailer: "Blizko::ResqueMailer" + # метод, который вызывается у отправщика (по умолчанию alert) + mail: alert ``` Для разработки можно (и нужно) создать файл `config/resque.local.yml`, в котором можно переопределить любые параметры: diff --git a/lib/resque/integration/configuration.rb b/lib/resque/integration/configuration.rb index ced2944..4b5a49c 100644 --- a/lib/resque/integration/configuration.rb +++ b/lib/resque/integration/configuration.rb @@ -23,6 +23,11 @@ def initialize(queue, config) super(data) end + # Returns workers count for given queue + def count + [super || 1, 1].max + end + # Returns hash of ENV variables that should be associated with this worker def env env = super || {} @@ -35,12 +40,54 @@ def env end end + # Failure notifier configuration + class Notifier < OpenStruct + def initialize(config) + super(config || {}) + end + + # Is notifier enabled + def enabled? + to.any? && enabled.nil? ? true : enabled + end + + # Returns true if payload should be included into reports + def include_payload? + include_payload.nil? ? + true : + include_payload + end + + # Returns recipients list + def to + super || [] + end + + # Returns sender address + def from + super || 'no_reply@gmail.com' + end + + # Returns mailer method + def mail + (super || :alert).to_sym + end + + # Returns mailer class + def mailer + super || 'ResqueFailedJobMailer::Mailer' + end + end + # Create configuration from given +paths+ def initialize(*paths) @configuration = {} paths.each { |f| load f } end + # Returns Resque redis configuration + # + # @return [OpenStruct] def redis @redis ||= OpenStruct.new :host => self['redis.host'] || 'localhost', :port => self['redis.port'] || 6379, @@ -49,18 +96,29 @@ def redis :namespace => self['redis.namespace'] end + # Returns workers configuration + # + # @return [Array] def workers @workers ||= (self[:workers] || {}).map { |k, v| Worker.new(k, v) } end + # Returns failure notifier config + def failure_notifier + @notifier ||= Notifier.new(self['failure.notifier']) + end + + # Returns Resque polling interval def interval (self['resque.interval'] || 5).to_i end + # Returns Resque verbosity level def verbosity (self['resque.verbosity'] || 0).to_i end + # Returns path to resque log file def log_file self['resque.log_file'] end diff --git a/lib/resque/integration/engine.rb b/lib/resque/integration/engine.rb index a9f8afb..2e7a7bf 100644 --- a/lib/resque/integration/engine.rb +++ b/lib/resque/integration/engine.rb @@ -1,14 +1,15 @@ # coding: utf-8 -require "rails/engine" +require 'rails/engine' +require 'active_support/core_ext/string/inflections' module Resque::Integration # Rails engine # @see http://guides.rubyonrails.org/engines.html class Engine < Rails::Engine rake_tasks do - load "resque/integration/tasks/resque.rake" - load "resque/integration/tasks/supervisor.rake" + load 'resque/integration/tasks/resque.rake' + load 'resque/integration/tasks/supervisor.rake' end initializer 'resque-integration.config' do @@ -25,6 +26,22 @@ class Engine < Rails::Engine Resque.redis.namespace = redis.namespace end + initializer 'resque-integration.failure_notifier' do + notifier = Resque.config.failure_notifier + + if notifier.enabled? + require 'resque_failed_job_mailer' + + Resque::Failure::Notifier.configure do |config| + config.to = notifier.to + config.from = notifier.from + config.include_payload = notifier.include_payload? + config.mail = notifier.mail + config.mailer = notifier.mailer.constantize + end + end + end + initializer 'resque-multi-job-forks.hook' do # Support for resque-multi-job-forks if ENV['JOBS_PER_FORK'] || ENV['MINUTES_PER_FORK'] diff --git a/lib/resque/integration/supervisor.rb b/lib/resque/integration/supervisor.rb index 299a6d6..cffbaf9 100644 --- a/lib/resque/integration/supervisor.rb +++ b/lib/resque/integration/supervisor.rb @@ -25,8 +25,17 @@ def start # write pid to file File.write(pid_file.to_s, Process.pid) + $0 = "Resque: supervisor" + loop do - Resque.workers.each(&:prune_dead_workers) + Resque.workers.each do |worker| + begin + worker.prune_dead_workers + rescue + # ignore + end + end + sleep(INTERVAL) end rescue diff --git a/resque-integration.gemspec b/resque-integration.gemspec index 96b003e..e79ab23 100644 --- a/resque-integration.gemspec +++ b/resque-integration.gemspec @@ -24,6 +24,7 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency 'resque-meta', '>= 2.0.0' gem.add_runtime_dependency 'resque-progress', '~> 1.0.1' gem.add_runtime_dependency 'resque-multi-job-forks', '~> 0.3.4' + gem.add_runtime_dependency 'resque-failed-job-mailer', '~> 0.0.3' gem.add_runtime_dependency 'multi_json' gem.add_runtime_dependency 'rake' diff --git a/spec/resque/integration/configuration_spec.rb b/spec/resque/integration/configuration_spec.rb new file mode 100644 index 0000000..0db6816 --- /dev/null +++ b/spec/resque/integration/configuration_spec.rb @@ -0,0 +1,85 @@ +# coding: utf-8 + +require 'spec_helper' + +describe Resque::Integration::Configuration::Notifier do + context 'when NilClass given as config' do + subject(:config) { described_class::new(nil) } + + it { should_not be_enabled } + its(:include_payload?) { should be_true } + its(:to) { should be_empty } + its(:from) { should eq 'no_reply@gmail.com' } + its(:mail) { should eq :alert } + its(:mailer) { should eq 'ResqueFailedJobMailer::Mailer' } + end + + context 'when Hash given as config' do + let :configuration do + {to: ['to1@mail', 'to2@mail'], + from: 'from@mail', + enabled: false, + include_payload: false, + mail: 'notify', + mailer: 'MyMailer'} + end + + subject(:config) { described_class::new(configuration) } + + it { should_not be_enabled } + its(:include_payload?) { should be_false } + its(:to) { should include 'to1@mail' } + its(:to) { should include 'to2@mail' } + its(:from) { should eq 'from@mail' } + its(:mail) { should eq :notify } + its(:mailer) { should eq 'MyMailer' } + end +end + +describe Resque::Integration::Configuration::Worker do + describe '.new' do + context 'when Integer given as config' do + subject(:config) { described_class::new(:default, 2) } + + its(:queue) { should eq :default } + its(:count) { should eq 2 } + end + + context 'when Hash given as config' do + subject(:config) { described_class::new(:default, :count => 2) } + + its(:queue) { should eq :default } + its(:count) { should eq 2 } + end + end + + describe '#count' do + context 'when initialized without count paramter' do + subject { described_class::new(:default, {}) } + + its(:count) { should eq 1 } + end + + context 'when initialized with count <= 0' do + subject { described_class::new(:default, :count => 0) } + + its(:count) { should eq 1 } + end + end + + describe '#env' do + let :config do + described_class::new(:default, + :count => 2, + :jobs_per_fork => 10, + :minutes_per_fork => 5, + :env => {:VAR => 2}) + end + subject { config.env } + + its([:QUEUE]) { should eq 'default' } + its([:JOBS_PER_FORK]) { should eq '10' } + its([:MINUTES_PER_FORK]) { should eq '5' } + its([:VAR]) { should eq '2' } + end +end \ No newline at end of file