I'm trying to reduce the amount of noise in my logs and would like to disable Rails from logging the stack trace during errors.
Since I am using an error reporting service (Honeybadger.io) I don't need to see the stack trace in the logs as it's already available in the exception report from the error handling service.
The default Rails middleware DebugExceptions is what logs the errors.
You can remove it with config.middleware.delete(ActionDispatch::DebugExceptions) in your config/environment.rb or config/environments/production.rb
According to the docs you should be able to add a backtrace silencer that excludes every line by returning true in the block.
But, at least with Rails 4.2.5.2, this doesn't appear to be working and even if it did work you would still end up with a line in log about the exception.
Accidentally I discovered that if you raise an error inside a silencer block that this will suppress the error message and the entire backtrace which turns out to be exactly what I'm looking for.
Rails.backtrace_cleaner.add_silencer { |_line| raise }
Combining this hack with the concise_logging gem I can now have logs that look like the following:
Well, Rails.backtrace_cleaner.add_silencer works, but I woulnd call its behaviour predictable
https://github.com/vipulnsward/rails/blob/ecc8f283cfc1b002b5141c527a827e74b770f2f0/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L155-L156
Since application_trace is empty(This is because error is not from user code but route error), we are falling back to framework_trace, which does not filter it (it filters only noise).
You can solve it with creating your own log_formatter. In your development.rb and/or test.rb add
config.log_formatter = SilentLogger.new
config.log_formatter.add_silencer { |line| line =~ /lib/ }
And create simple class in models with only method call required. There you can modify your backtrace as you wish.
class SilentLogger
def initialize
#silencers = []
end
def add_silencer(&block)
#silencers << block
end
def call(severity, timestamp, progname, msg)
backtrace = (String === msg) ? "#{msg}\n" : "#{msg.inspect}\n"
return backtrace if #silencers.empty?
#silencers.each do |s|
backtrace = backtrace.split("\n").delete_if { |line| s.call(line) }
end
backtrace.join("\n")
end
end
Related
Problem: When running specs the output is very confusing, instead of saying where the error lies it just throws misleading errors
This is inside the rails lib folder, and it's mounted on the routes.rb
# lib/engines/users/app.rb
module Engines
module Users
class App < Sinatra::Base
get '/api/v1/users/me' do
raise 'runtime error here'
end
get '/api/v1/another-route' do
# something here
status 200
end
end
end
end
The spec file looks something like this:
it 'returns a 200' do
get '/api/v1/users/me', auth_attributes
expect(last_response.body).to eq 'something' # added to clarify my point, it's not the response status that I care of.
expect(last_response.status).to be 200
end
error:
Failure/Error: expect(last_response.status).to be 200
expected #<Fixnum:401> => 200
got #<Fixnum:1001> => 500
Compared using equal?, which compares object identity,
but expected and actual are not the same object. Use
`expect(actual).to eq(expected)` if you don't care about
object identity in this example.
expected error:
RuntimeError:
runtime error here
Another route also fails:
it 'something' do
get '/api/v1/another-route', auth_attributes
expect(last_response.status).to be 401
json = JSON.parse(last_response.body).with_indifferent_access
expect(json[:message]).to eql "You have been revoked access."
end
error: Prints a massive html output which I believe is the rails backtrace html output
expected error: none as this endpoint doesn't raise an error
My question is if there's a way to:
Stop rails from dealing with this, so it gives the actual output
Avoid the entire engine to fail because one route raise exception
I believe that by solving the first point, the second one gets fixed too.
Thank you for your time
In order to solve my problem I've discovered that on the spec_helper the ENV['RACK_ENV'] was not being set, setting it to test resolves the problem and throws the exception I need in order to debug my code.
This happens because https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1832
set :show_exceptions, Proc.new { development? }
development? returned true when in fact should be false (needed the RACK_ENV set to test)
Now I get the correct output.
Used rake rails:update, meticulously updated overwritten files, and have my rspec specs running green. But when I run rails s I hit this:
Unexpected error while processing request: stack level too deep
/Users/Alex/.rvm/gems/ruby-2.0.0-p451/gems/activesupport-4.1.4/lib/active_support/cache/strategy/local_cache_middleware.rb:33
Specifically it is crapping out at response = #app.call(env) (line 26) in the above cited file.
I'm working through checklists to see if I might have missed a configuration setting somewhere. Can anyone give me a clue?
So, first thing I did was get a full backtrace out of the exception by adding:
rescue Exception => e
puts e.backtrace
LocalCacheRegistry.set_cache_for(local_cache_key, nil)
raise
end
Which then revealed one critical line before the cache middleware one I saw before:
/Users/Alex/.rvm/gems/ruby-2.0.0-p451/gems/activesupport-4.1.4/lib/active_support/logger.rb:38
Unexpected error while processing request: stack level too deep
/Users/Alex/.rvm/gems/ruby-2.0.0-p451/gems/activesupport-4.1.4/lib/active_support/cache/strategy/local_cache_middleware.rb:35
I jumped into the listed line which looked like this:
define_method(:level=) do |level|
logger.level = level
super(level)
end
Then searched the rest of my repo to see where I was touching logger.level. (If I hadn't been able to find the call that way, I would've used Kernel#caller.) Ahah, I discover: config/initializers/quiet_assets.rb, hmm what is this? Looks like a monkey-patch I put in an initializer ages ago:
# taken from https://stackoverflow.com/questions/6312448/how-to-disable-logging-of-asset-pipeline-sprockets-messages-in-rails-3-1
if Rails.env.development?
Rails.application.assets.logger = Logger.new('/dev/null')
Rails::Rack::Logger.class_eval do
def call_with_quiet_assets(env)
previous_level = Rails.logger.level
Rails.logger.level = Logger::ERROR if env['PATH_INFO'] =~ %r{^/assets/}
call_without_quiet_assets(env)
ensure
Rails.logger.level = previous_level
end
alias_method_chain :call, :quiet_assets
end
end
When I commented this out, poof, my error was gone and I was able to load up a page in browser. Now I've deleted the initializer and am good to go. :) For those that used How to disable logging of asset pipeline (sprockets) messages in Rails 3.1?, make sure to remove it when you upgrade!
I'm writing tests for a Ruby Rails application, and I have a block of code that is supposed to catch an error thrown by my Redis server if Ruby cannot connect to it. Currently, the code looks like this:
begin
config.before(:all) { Resque.redis.select 1 }
config.after(:all) { Resque.redis.keys("queue:*").each { |key| Resque.redis.del key } }
rescue Exception
puts "RESCUED REDIS ERROR"
end
According to the stack trace when I try to run the tests, the second line of that code snippet -- config.before(:all) {...} -- throws a Redis::CannotConnectError. After a lot of "e.class.superclass.superclass..." commands, I determined that this error inherited from StandardError.
After that I got stuck. I tried catching the error with "rescue Redis::CannotConnectError", then "rescue", and finally "rescue Exception", but the error is still thrown. However, I tried the same things in the Ruby command prompt, and the exception was caught every time
Could anyone help me work out what's happening here? Thanks!
The problem is that the blocks passed to before and after are not being executed at the time they're defined; instead, they're being stored and then called later by Rspec before and after each spec file runs.
You'll probably want to move the begin/rescue within the blocks instead:
config.before(:all) do
begin
Resque.redis.select 1
rescue Exception
puts "RESCUED REDIS ERROR"
end
end
# same for config.after(:all)
This all worked fine in rails 2.3.5, but when a contractor firm upgraded directly to 2.3.14 suddenly all the integration tests were saying:
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[]
I have a before_filter on my ApplicationController that sets a bunch of cookies for some javascript to use, and I found that if I comment out all but one of the lines that sets cookie values, it works fine, and it doesn't matter which line I leave in.
before_filter :set_cookies
def set_cookies
cookies['logged_in'] = (logged_in ? 'y' : 'n')
cookies['gets_premium'] = (gets_premium ? 'y' : 'n')
cookies['is_admin'] = (is_admin ? 'y' : 'n')
end
If only one of these lines is active, everything is fine in the integration test, otherwise I get the error above. For example, consider the following test / response:
test "foo" do
get '/'
end
$ ruby -I"lib:test" test/integration/foo_test.rb -n test_foo -v
Loaded suite test/integration/foo_test
Started
test_foo(FooTest): E
Finished in 5.112648 seconds.
1) Error:
test_foo(FooTest):
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[]
test/integration/foo_test.rb:269:in `test_foo'
1 tests, 0 assertions, 0 failures, 1 errors
But if any two of those cookie setting lines are commented out, I get:
$ ruby -I"lib:test" test/integration/foo_test.rb -n test_foo -v
Loaded suite test/integration/foo_test
Started
test_foo(FooTest): .
Finished in 1.780388 seconds.
1 tests, 0 assertions, 0 failures, 0 errors
The website running in development and production mode works fine - this is just a matter of getting the tests passing. Also, with debugging output I have verified that the error does not get thrown in the method where the cookies get set, that all executes fine, it's somewhere later that the error happens (but the backtrace doesn't tell me where)
This turned out to be a bug in how rack rack writes cookies to the client along with the session cookie. Rack was including double newlines and omitting semi-colons.
Browsers like firefox can handle mildly malformed cookie data, but the integration test client couldn't.
To fix this I had to rewrite the cookie header before sending it to the client.
In environment.rb:
require 'rack_rails_cookie_header_hack'
And in lib/rack_rails_cookie_header_hack.rb:
class RackRailsCookieHeaderHack
def initialize(app)
#app = app
end
def call(env)
status, headers, body = #app.call(env)
if headers['Set-Cookie']
cookies = headers['Set-Cookie']
cookies = cookies.split("\n") if is_str = cookies.is_a?(String)
if cookies.respond_to?(:collect!)
cookies.collect! { |h| h.strip }
cookies.delete_if { |h| h.empty? }
cookies.collect! { |h| h.include?(';') ? h : h + ';' }
end
headers['Set-Cookie'] = is_str ? cookies.join("\n").strip : cookies
end
[status, headers, body]
end
end
Sorry the sources aren't articulated, I actually fixed this awhile ago and came across this questions and figured I'd post my patch.
L0ne's answer works for me, but you also need to include this - it can go at the bottom of lib/rack_rails_cookie_header_hack.rb, providing you're requiring it at the bottom of your environment.rb file - ie after the Rails::Initializer has run:
ActionController::Dispatcher.middleware.insert_before(ActionController::Base.session_store, RackRailsCookieHeaderHack)
Old forgotten issues...
I haven't tested Lone's fix but he has correctly identified the problem. If you catch the exception and print using exception.backtrace, you'll see that the problem is caused by
gems/actionpack/lib/action_controller/integration.rb:329
The offending code is this:
cookies.each do |cookie|
name, value = cookie.match(/^([^=]*)=([^;]*);/)[1,2]
#cookies[name] = value
end
If you're like me, and you're only interested in some super quick integration tests, and don't care too much about future maintainability (since it's rails 2), then you can just add a conditional filter in that method
cookies.each do |cookie|
unless cookie.blank?
name, value = cookie.match(/^([^=]*)=([^;]*);/)[1,2]
#cookies[name] = value
end
end
and problem solved
I am working on rails project and I am trying to get exceptions to be logged to the rails log files. I know I can call logger.error $! to get the first line of the exception logged to the file. But, I want to get the entire trace stack logged as well. How do I log the entire trace back of an exception using the default rails logger?
logger.error $!.backtrace
Also, don't forget you can
rescue ErrorType => error_name
to give your error a variable name other than the default $!.
The way rails does it is
137 logger.fatal(
138 "\n\n#{exception.class} (#{exception.message}):\n " +
139 clean_backtrace(exception).join("\n ") +
140 "\n\n"
141 )
248 def clean_backtrace(exception)
249 if backtrace = exception.backtrace
250 if defined?(RAILS_ROOT)
251 backtrace.map { |line| line.sub RAILS_ROOT, '' }
252 else
253 backtrace
254 end
255 end
256 end
In later versions of Rails, simply uncomment the following line in RAIL_ROOT/config/initializers/backtrace_silencers.rb (or add this file itself if it's not there):
# Rails.backtrace_cleaner.remove_silencers!
This way you get the full backtrace written to the log on an exception. This works for me in v2.3.4.
logger.error caller.join("\n") should do the trick.
In Rails, ActionController::Rescue deals with it. In my application controller actions, i'm using method log_error from this module to pretty-format backtrace in logs:
def foo_action
# break something in here
rescue
log_error($!)
# call firemen
end
Here's how I would do it:
http://gist.github.com/127708
Here's the ri documentation for Exception#backtrace:
http://gist.github.com/127710
Note that you could also use Kernel#caller, which gives you the full trace (minus the curent frame) as well.
http://gist.github.com/127709
Also - Note that if you are trying to catch all exceptions, you should rescue from Exception, not RuntimeError.
You can also use ruby's default variables, like this:
logger.error "Your error message. Exception message:#{$!} Stacktrace:#{$#}"