如何在Rails 3中使用delayed_job使ExceptionNotifier正常工作?

26
我希望 ExceptionNotifier 在延时任务中发生异常时能够像处理其他异常一样发送电子邮件。我该如何实现这一点?

是的,我看过这两个,但我认为这些解决方案只适用于Rails 2。 - Alex Korban
6个回答

23

我是用 Rails 3.2.6、delayed_job 3.0.3 和 exception_notification 2.6.1 gem 来做这件事情的。

# In config/environments/production.rb or config/initializers/delayed_job.rb

# Optional but recommended for less future surprises.
# Fail at startup if method does not exist instead of later in a background job 
[[ExceptionNotifier::Notifier, :background_exception_notification]].each do |object, method_name|
  raise NoMethodError, "undefined method `#{method_name}' for #{object.inspect}" unless object.respond_to?(method_name, true)
end

# Chain delayed job's handle_failed_job method to do exception notification
Delayed::Worker.class_eval do 
  def handle_failed_job_with_notification(job, error)
    handle_failed_job_without_notification(job, error)
    # only actually send mail in production
    if Rails.env.production?
      # rescue if ExceptionNotifier fails for some reason
      begin
        ExceptionNotifier::Notifier.background_exception_notification(error)
      rescue Exception => e
        Rails.logger.error "ExceptionNotifier failed: #{e.class.name}: #{e.message}"
        e.backtrace.each do |f|
          Rails.logger.error "  #{f}"
        end
        Rails.logger.flush
      end
    end
  end 
  alias_method_chain :handle_failed_job, :notification 
end

在所有环境中加载此代码,以便在 bundle update 等操作后捕获错误,避免错误进入生产环境。 我通过拥有一个 config/initializers/delayed_job.rb 文件来实现此功能,但您也可以为每个 config/environments/* 环境复制该代码。

另一个提示是稍微调整延迟作业配置,因为默认情况下,当作业失败时可能会收到大量重复的异常邮件。

# In config/initializers/delayed_job_config.rb
Delayed::Worker.max_attempts = 3

更新:我遇到了一些delayed_job守护进程默默退出的问题,原来是当ExceptionNotifier无法发送邮件且没有人捕获异常时。现在代码进行了修复以捕获和记录异常。


1
我建议使用官方的异常通知 gem,网址为 https://github.com/smartinez87/exception_notification。 - Christopher Manning
好的,我会更新到那个版本。当时我使用了rails3 gem,因为官方版本似乎存在一些Rails 3的问题。 - Mattias Wadman
+1 这种方法适用于Rails 3.2.6、Delayed Job 3.0.3和Exception Notification 2.6.1。起初我认为使用内置的钩子添加一个错误方法会是一个更干净的方法,但是这必须为每个作业类定义,并且由于delegate的使用方式而不能通用地为“PerformableMethod”定义它。 - Nathan
不错。其实我前几天已经更新到了相同的版本,但是忘记更新答案了。 - Mattias Wadman
3
我在方法调用中添加了, :data => {:job => job},这样我就可以获得更多的细节信息了。 - mat
1
为了仅在最后一次尝试失败时收到通知,我添加了以下代码:ExceptionNotifier.notify_exception(error) if job.attempts == Delayed::Worker.max_attempts替换原有代码:ExceptionNotifier::Notifier.background_exception_notification(error) - micred

6

补充@MattiasWadman的答案,自从exception_notification 4.0 有一种新的方式来处理手动通知。所以现在可以这样做:

ExceptionNotifier::Notifier.background_exception_notification(error)

使用
ExceptionNotifier.notify_exception(error)

这个也会在后台处理吗? - Daniël Zwijnenburg
1
@DaniëlZwijnenburg 这个方法是被delayed_job调用的,所以是的。 - Alter Lagos

4

处理异常的另一种方式(作为初始化器):

class DelayedErrorHandler < Delayed::Plugin

  callbacks do |lifecycle|

    lifecycle.around(:invoke_job) do |job, *args, &block|

      begin
        block.call(job, *args)
      rescue Exception => e

        # ...Process exception here...

        raise e
      end
    end
  end
end

Delayed::Worker.plugins << DelayedErrorHandler

我认为这是最干净的解决方案。顺便说一下,其他解决方案在未来不会起作用,因为alias_method_chain在Rails 5中已被弃用。 - remo

3

alias_method_chain在Rails 5中已经不存在。

以下是使用Ruby 2的prepend进行替代的新方法(正确的方法)

# In config/initializers/delayed_job.rb
module CustomFailedJob
  def handle_failed_job(job, error)
    super
    ExceptionNotifier.notify_exception(error, data: {job: job})
  end
end

class Delayed::Worker
  prepend CustomFailedJob
end

这是对我有用的答案,运行Rails 4.2。 - Daniel Hollands
1
Delayed::Worker.prepend CustomFailedJob 可以直接使用。 - Andres Leon

2

针对异常通知(exception_notification)3.0.0版本的更改:

ExceptionNotifier::Notifier.background_exception_notification(error)

to:

ExceptionNotifier::Notifier.background_exception_notification(error).deliver

0

更简单和更新的答案:

# Chain delayed job's handle_failed_job method to do exception notification
Delayed::Worker.class_eval do
  def handle_failed_job_with_notification job, error
    handle_failed_job_without_notification job, error
    ExceptionNotifier.notify_exception error,
      data: {job: job, handler: job.handler} rescue nil
  end 
  alias_method_chain :handle_failed_job, :notification
end

在控制台上进行测试:

Delayed::Job.enqueue (JS=Struct.new(:a){ def perform; raise 'here'; end }).new(1)

1
@AlterLagos 我认为你误解了alias_method_chain。 - kuboon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接