Rails Exception Notification without middleware - ruby-on-rails

I have an Rails 4 application that is entirely comprised of rails runners over cron generated from the whenever gem.
I'd like to be notified if there are any exceptions that occur during the run. The exception_notification gem only runs as rack middleware (web requests only), so it doesn't handle rails runners.
Any ideas? I'm looking to get notified over email or on slack.

You can use ExceptionNotifier.notify_exception in a rescue block to send a notification.
For example:
def rescue_exception(data)
yield
rescue => e
ExceptionNotifier.notify_exception(e, data: data)
end
every :hour do
rescue_exception(runner: 'SomeModel.some_method') do
runner "SomeModel.some_method"
end
end
Please refer to https://github.com/smartinez87/exception_notification#background-notifications. Use data hash to pass additional information about the context.

Related

Testing unmarshalling of asynchronous ActionMailer methods using Sidekiq

TLDR; How can I test that a PORO argument for an asynchronous ActionMailer action (using Sidekiq) serializes and deserializes correctly?
Sidekiq provides RSpec matchers for testing that a job is enqueued and performing a job (with given arguments).
--
To give you some context, I have a Ruby on Rails 4 application with an ActionMailer. Within the ActionMailer is a method that takes in a PORO as an argument - with references to data I need in the email. I use Sidekiq to handle the background jobs. It turns out that there was an issue in deserializing the argument that it would fail when Sidekiq decided to perform the job. I haven't been able to find a way to test the correctness of the un/marshaling such that the PORO I called the action with is being used when performed.
For example:
Given an ActionMailer with an action
class ApplicationMailer < ActionMailer::Base
def send_alert profile
#profile = profile
mail(to: profile.email)
end
end
...
I would use it like this
profile = ProfileDetailsService.new(user)
ApplicationMailer.send_alert(profile).deliver_later
...
And have a test like this (but this fails)
let(:profile) { ProfileDetailsService.new(user) }
it 'request an email to be sent' do
expect {
ApplicationMailer.send_alert(profile).deliver_later
}.to have_enqueued_job.on_queue('mailers')
end
Any assistance would be much appreciated.
You can test it in a synchronous way (using only IRB and without the need of start the Sidekiq workers).
Let's say your worker class has the following implementation:
class Worker
include Sidekiq::Worker
def perform(poro_obj)
puts poro_obj.inspect
# ...
end
end
You can open IRB (bundle exec irb) and type the following commands:
require 'sidekiq'
require 'worker'
Worker.new.perform
Thus, you will execute the code in a synchronous way AND using Sidekiq (note that we're invoking the perform method instead of the perform_async).
I think this is the best way to debug your code.

Sidekiq Bugsnag Middleware

Is there a way to integrate Bugsnag to rescue all exceptions for Sidekiq and send notifications? I can't find it in the docs.
def call(_worker, _msg, _queue)
Bugsnag.before_notify_callbacks << lambda do |notif|
notif.add_tab(
:gem_version,
metascrape: Metascrape::VERSION
)
end
yield
rescue ActiveRecord::RecordNotFound => e
Bugsnag.notify e
ensure
Bugsnag.before_notify_callbacks.clear
end
end
Why do you think you need to do anything? The docs indicate it should just work.
Bugsnag ruby works out of the box with Rails, Sidekiq, Resque, DelayedJob (3+), Mailman, Rake and Rack. It should be easy to add support for other frameworks, either by sending a pull request here or adding a hook to those projects.
https://bugsnag.com/docs/notifiers/ruby

Ruby - Asynchronous Mailing

I'm quite new to Ruby on Rails and want to send an e-mail asynchronously to make my app respond faster. The synchronous sending works fine and would be my alternative.
I've found some sites and defined the gems 'resque_mailer', 'redis' and 'resque' in my Gemfile and installed them via the bundle install command. My mailer looks like:
class Contact < ActionMailer::Base
include Resque::Mailer
default from: "<private>"
def contact(email, bandName, respondMail, message)
#message = message
#respondMail = respondMail
#bandName = bandName
mail(:to => email,
:subject => "subject")
end
end
The Controller-Call to send the mail looks like:
Contact.contact(band.email, band.name, params[:respondMail].to_s, params[:message].to_s).deliver
If I try to send my mail now asynchronously I'm getting the following error which is caused by the "contact" call:
Timed out connecting to Redis on 127.0.0.1:6379
Unfortunately I've got no idea how to solve this. Can someone please tell me which steps I must make to send a mail asynchronously? If possible there should be no extra database columns.
In order for resque and resque_mailer to work you need to install redis. You can find more about redis here: http://redis.io/download
If you are new to rails, all the external tools and dependencies can be quite overwhelming. If you don't have an application with extreme load you should consider using a simpler job queue like https://github.com/collectiveidea/delayed_job. It does not depend on further external services and uses your current database to store the jobs. The only thing you need to do is to keep the workers up.

Problem using Resque, Rails 3 and Active-Recored

I have this Class which response to perform to be run by "Resque", I have an error at this line recipient.response = response.body which is:
undefined method response=' for #Hash:0x00000003969da0
I think that because the worker and ActiveRecord can't work together.
P.S I already loaded my environment and this class placed in lib directory
Using:
Ruby 1.9.2
Rails 3
Resque 1.10.0
class Msg
def self.perform(message,sender,host, path, recipient)
message_logger ||= Logger.new("#{Rails.root}/log/message.log")
response = Net::HTTP.get_response(host, path)
begin
recipient.response = response.body
recipient.sent_at = Time.zone.now
recipient.save
# Logging
log = "Message #{
message.sent_at}\n\tRespone:\n\t\tBody: #{response.body}\n\t\tCode: #{response.code}\n"
message_logger.info(log)
rescue Exception => e
message_logger.error(e.message + '/n' + e.backtrace.inspect)
end
end
end
Resque uses json serialization. JSON serialization would not allow you to deserialize an object with the method intact.
If you have an instance of Recipient (named "recipient") and want to use it in the method to perform/persist a response then you should enqueue the id of the recipient and fetch it from your persistence layer when perform is called.
https://github.com/defunkt/resque (checkout the section on Persistence)
Resque is different from DelayedJob/Background Job and other in this way. (which is why I like it. the same queue can be shared by multiple ruby implementations, jruby, mri, ...)
That doesn't sound like an issue with resque and activerecord at all. It says the parameter recipient that you passed in was a hash. Where's the code that enqueued the job? You can also take a look a the log output from the worker where you saw that error message to see what the parameters passed into the job were.

How do i integrate Hoptoad with DelayedJob and DaemonSpawn?

I have been happily using the DelayedJob idiom:
foo.send_later(:bar)
This calls the method bar on the object foo in the DelayedJob process.
And I've been using DaemonSpawn to kick off the DelayedJob process on my server.
But... if foo throws an exception Hoptoad doesn't catch it.
Is this a bug in any of these packages... or do I need to change some configuration... or do I need to insert some exception handling in DS or DJ that will call the Hoptoad notifier?
In response to the first comment below.
class DelayedJobWorker < DaemonSpawn::Base
def start(args)
ENV['RAILS_ENV'] ||= args.first || 'development'
Dir.chdir RAILS_ROOT
require File.join('config', 'environment')
Delayed::Worker.new.start
end
Try monkeypatching Delayed::Worker#handle_failed_job :
# lib/delayed_job_airbrake.rb
module Delayed
class Worker
protected
def handle_failed_job_with_airbrake(job, error)
say "Delayed job failed -- logging to Airbrake"
HoptoadNotifier.notify(error)
handle_failed_job_without_airbrake(job, error)
end
alias_method_chain :handle_failed_job, :airbrake
end
end
This worked for me.
(in a Rails 3.0.10 app using delayed_job 2.1.4 and hoptoad_notifier 2.4.11)
Check out the source for Delayed::Job... there's a snippet like:
# This is a good hook if you need to report job processing errors in additional or different ways
def log_exception(error)
logger.error "* [JOB] #{name} failed with #{error.class.name}: #{error.message} - #{attempts} failed attempts"
logger.error(error)
end
I haven't tried it, but I think you could do something like:
class Delayed::Job
def log_exception_with_hoptoad(error)
log_exception_without_hoptoad(error)
HoptoadNotifier.notify(error)
end
alias_method_chain :log_exception, :hoptoad
end
Hoptoad uses the Rails rescue_action_in_public hook method to intercept exceptions and log them. This method is only executed when the request is dispatched by a Rails controller.
For this reason, Hoptoad is completely unaware of any exception generated, for example, by rake tasks or the rails script/runner.
If you want to have Hoptoad tracking your exception, you should manually integrate it.
It should be quite straightforward. The following code fragment demonstrates how Hoptoad is invoked
def rescue_action_in_public_with_hoptoad exception
notify_hoptoad(exception) unless ignore?(exception) || ignore_user_agent?
rescue_action_in_public_without_hoptoad(exception)
end
Just include Hoptoad library in your environment and call notify_hoptoad(exception) should work. Make sure your environment provides the same API of a Rails controller or Hoptoad might complain.
Just throwing it out there - your daemon should require the rails environment that you're working on. It should look something along the lines of:
RAILS_ENV = ARGV.first || ENV['RAILS_ENV'] || 'production'
require File.join('config', 'environment')
This way you can specify environment in which daemon is called.
Since it runs delayed job chances are daemon already does that (it needs activerecord), but maybe you're only requiring minimal activerecord to make delayed_job happy without rails.

Resources