Catch Exceptions Application Wide - ruby-on-rails

Is there a way of overriding default Exception handling (default rescue), so that I can write custom logic there, for example sending stacktrace to our Slack channel.
Basically I want to make something what NewRelic does with their gem: https://github.com/newrelic/rpm but more local version of it.
Thanks

The simplest thing you could do is instrument all error raising using a TracePoint:
trace = TracePoint.new(:raise) do |tp|
myLogger.log [tp.lineno, tp.event, tp.raised_exception]
end
trace.enable
Using traces used to cause significant code slowdown. I think the new API in ruby 2.0 reduced the slowdown notably. According to my naive benchmark:
count = 10_000_000
t = Time.now
count.times do |i|
begin
0 / 0
rescue
end
end
puts "trial 1: #{Time.now - t}"
t = Time.now
count.times do |i|
"hi"
begin
0 / 0
rescue
end
end
puts "trial 2: #{Time.now - t}"
trace = TracePoint.new(:raise) do |tp|
"hi"
end
trace.enable
t = Time.now
count.times do |i|
begin
0 / 0
rescue
end
end
puts "trial 3: #{Time.now - t}"
#=>trial 1: 10.110471094
#=>trial 2: 9.971755759
#=>trial 3: 11.608365399
Tracepoint only adds 1 second (or 10%) slowdown in 10,000,000 executions of raise. That being said, TracePoint still isn't considered a "production-worthy technique" because it does add overhead and can be hard to predict (eg there are a lot of obscure exceptions in ruby).
If you're wondering how new relic manages to instrument code without overhead:
...
class_eval <<-EOC
def #{with_method_name}(*args, &block)
perform_action_with_newrelic_trace(#{argument_list.join(',')}) do
#{without_method_name}(*args, &block)
end
end
EOC
...
It uses several hundred lines of meta-programming to capture specific methods, deconstruct them, and redefine them with instrumentation inside. This technique requires a lot of code and (I would assume) extra memory and time at start-up, but has the advantage of no additional overhead once the methods are assigned.

Try something like this in application_controller.rb
rescue_from Exception, with: :rescue500 if Rails.env.production?
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found unless Rails.env.development?
def record_not_found
render json: {code: 404, errors: ["Record not found"]}, status: 404
end
def rescue404
render json: {code: 404, errors: ["No round matches"]}, status: 404
end
def rescue500(ex)
Rails.logger.error("\n\nEXCEPTION: #{ex.inspect}\n")
Rails.logger.info(ex)
render json: {code: 500, errors: [ex.message] }, status: 500
end

Related

How to handle external service failure in Open-Uri?

In my Rails app I am trying to fetch a number of currency exchange rates from an external service and store them in the cache:
require 'open-uri'
module ExchangeRate
def self.all
Rails.cache.fetch("exchange_rates", :expires_in => 24.hours) { load_all }
end
private
def self.load_all
hashes = {}
CURRENCIES.each do |currency|
begin
hash = JSON.parse(open(URI("http://api.fixer.io/latest?base=#{currency}")).read) #what if not available?
hashes[currency] = hash["rates"]
rescue Timeout::Error
puts "Timeout"
rescue OpenURI::Error => e
puts e.message
end
end
hashes
end
end
This works great in development but I am worried about the production environment. How can I prevent the whole thing from being cached if the external service is not available? How can I ensure ExchangeRate.all always contains data, even if it's old and can't be updated due to an external service failure?
I tried to add some basic error handling but I'm afraid it's not enough.
If you're worried about your external service not being reliable enough to keep up with caching every 24 hours, then you should disable the auto cache expiration, let users work with old data, and set up some kind of notification system to tell you if the load_all fails.
Here's what I'd do:
Assume ExchangeRate.all always returns a cached copy, with no expiration (this will return nil if no cache is found):
module ExchangeRate
def self.all
rates = Rails.cache.fetch("exchange_rates")
UpdateCurrenciesJob.perform_later if rates.nil?
rates
end
end
Create an ActiveJob that handles the updates on a regular basis:
class UpdateCurrenciesJob < ApplicationJob
queue_as :default
def perform(*_args)
hashes = {}
CURRENCIES.each do |currency|
begin
hash = JSON.parse(open(URI("http://api.fixer.io/latest?base=#{currency}")).read) # what if not available?
hashes[currency] = hash['rates'].merge('updated_at' => Time.current)
rescue Timeout::Error
puts 'Timeout'
rescue OpenURI::Error => e
puts e.message
end
if hashes[currency].blank? || hashes[currency]['updated_at'] < Time.current - 24.hours
# send a mail saying "this currency hasn't been updated"
end
end
Rails.cache.write('exchange_rates', hashes)
end
end
Set the job up to run every few hours (4, 8, 12, less than 24). This way, the currencies will load in the background, the clients will always have data, and you will always know if currencies aren't working.

Printing error when using PARAMS in Rails

For my API in RAILS I have programmed a code that basically does the following.
class Api::V1::NameController < ApplicationController
skip_before_filter :verify_authenticity_token
def index
end
def create
# Loading data
data_1_W = params[:data1]
data_2_W = params[:data2]
while len > i
# -Here I do some calculations with data_1_W and data_2_W.
# Its not important to show the code here
end
# -Organizing outputs to obtain only one JSON-
# Its not important to show the code here
# Finally HTTP responses
if check_error == 1
render status: 200, json: {
message: "Succesful data calculation",
data_output: response_hash
}.to_json
end
end
end
To test that everything is working I use the cURL command. I notice that loading the data could be a problem and therefore the code would break.
I want to tell the user that it was an error loading the data for some reason (HTTP response), but I don't know where to put it. If I put and else under my success status it would not print it because the code breaks just starting (instead of sending the correct name - d '#data.json' of the data in cURL I send -d '#dat.json').
The data I am loading is a JSON data {"data1":[{"name1":"value1"},{"name2":number2}...],"data2":[{"name1":"value1"},{"name2":number2...}]}. (This data has 70080 rows with 2 columns if we see it as a table, which I divided into two in my CODE for calculations purposes data_1_W and data_2_W)
Could anyone help me where to put it? more or less like this:
render status: 500, json: {
message: "Error loading the data",
}.to_json
Put it in a rescue block around the code that throws the error.
E.g.
def func
# code that raises exception
rescue SomeException => e
# render 422
end
Since you are working in Rails I'd recommend going the rails way. This means that I would create some kind of service and initialize it in the create action.
Now, within the service you do all you funky stuff (which also allows you to clean this controller and make i look prettier) and the moment a condition is not fulfilled in that service return false. So...
# controllers/api/v1/name_controller.rb
...
def create
meaningful_variable_name = YourFunkyService.new(args)
if meaningful_variable_name.perform # since you are in create then I assume you're creating some kind of resource
#do something
else
render json: {
error: "Your error",
status: error_code, # I don't think you want to return 500. Since you're the one handling it
}
end
end
# services/api/v1/your_funky_service.rb
class Api::V1::YourFunkyService
def initiliaze(params)
#params = params
end
def perfom #call it save if you wish
....
return false if check_error == 1
end
end

Continue assertion after failures in ruby

My assertion example is below,
class test < Test::Unit::TestCase
def test_users
begin
assert_equal(user.name, 'John')
assert_equal(user.age, 30)
assert_equal(user.zipcode, 500002)
rescue Exception
raise
end
end
end
If any one of assertions fails, i should move on to process the next one and collect the failure assertions and show failures the end of the result.
I have used add_failure method, its working for looping condition
rescue Test::Unit::AssertionFailedError => e
add_failure(e.message, e.backtrace)
Can any one help ?
A good unit test should test exactly one thing, specifically to avoid problems like you just face. This test case will report on all failed tests, and not just the first failed test:
class MyTest < Test::Unit::TestCase
def test_user_name
assert_equal(user.name, 'John')
end
def test_user_age
assert_equal(user.age, 30)
end
def test_user_zipcode
assert_equal(user.zipcode, 500002)
end
end
Your main problem is that assert_equal ends up calling assert (as shown below) and assert will raise an ArgumentException.
File test/unit/assertions.rb, line 144
def assert_equal(exp, act, msg = nil)
msg = message(msg) {
# omitted code to save space
}
assert(exp == act, msg)
end
File test/unit/assertions.rb, line 29
def assert(test, msg = UNASSIGNED)
case msg
when UNASSIGNED
msg = nil
when String, Proc
else
bt = caller.reject { |s| s.rindex(MINI_DIR, 0) }
raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt
end
super
end
You could extend Test::Unit::Assertions and provide an assertion that does not raise ArgumentError, which is what is stopping continuation past the failed assertion.
See this question for advice on going that direction and adding in safe assertions.
Please find this code for Continue assertion after failures in Ruby :
def raise_and_rescue
begin
puts 'I am before the raise.'
raise 'An error has occured.'
puts 'I am after the raise.'
rescue
puts 'I am rescued.'
end
puts 'I am after the begin block.'
end
Output :
ruby p045handexcp.rb
I am before the raise.
I am rescued.
I am after the begin block.
Exit code: 0

How do I make the loop finish when an error is thrown inside?

If I have a loop, and somewhere in the loop, I get an exception or error. How do I keep the loop going?
Foos.each do |foo|
....
# Random error/exception thrown here
....
end
Should I have a rescue block in the loop? Will that make the loop finish? Or is there a better alternative?
You can use add a begin/rescue block. I am not sure there is other ways to do keep loop going if an error is raised.
4.times do |i|
begin
raise if i == 2
puts i
rescue
puts "an error happened but I'm not done yet."
end
end
# 0
# 1
# an error happened but I'm not done yet.
# 3
#=> 4
Since your title in the other hand ask for a way to ends the loop.
If you want the loop to ends in the rescue, you can use break.
4.times do |i|
begin
raise if i == 2
puts i
rescue
puts "an error happened and I'm done."
break
end
end
# 0
# 1
# an error happened and I'm done.
#=> nil

nested loop in show at controller in Ruby optimization

#release = Release.find(params[:id])
#release_cycles=#release.cycles
#release_cycles=Cycle.find_by_sql("select * from cycles where release_id=#{params[:id]}")
current_page=params[:page]?Integer(params[:page]):1
#release_cycles = #release_cycles.paginate(:page=>params[:page],:per_page=>5)
release_ics=#release.ics
puts "params[releases==]==#{params[:releases]}"
releases=params[:releases].to_i
release1=(releases>0)?Release.find(params[:releases]):nil
puts "release1==#{release1}"
#non_ics=(release1!=nil)?(release1.ics):Ic.active
#non_members=[]
#non_ics.each do |non_ic|
check=1
release_ics.each do |release_ic|
if non_ic==release_ic
check=0
puts "inside ics comparison if"
end
end
if check==1
puts "inside if ! in release_only"
#non_members << non_ic
puts "#ics==#{#non_members}"
end
end
...
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #release }
end
end
The commented block of code at the end is eating up runtime like crazy (takes about 20-30 seconds to load) I think I have an idea on how to optimize this but I would like a third person thought on how to optimize the code to make it go faster
Your entire top section of the code can be replaced by 4 lines of code:
#release = Release.find(params[:id])
#release_cycles = #release.cycles.paginate(:page=> params[:page].presence || 1,
:per_page=>5)
#non_ics= params[:releases].present? ? Release.find(params[:releases]).ics :
Ic.active
#non_members = #non_ics - #release.ics
Apart from code that can be improved you are loading all the releases in to memory and paginating the result set in ruby memory space. Which can slow your process down if you have a large number of cycles for each release.
I calculated the intersection between the two arrays in the last line using Ruby. If the array size is big I would use SQL for that.

Resources