When using custom exceptions_app and rescue_responses, an application has more control of uncaught exceptions and an excessive logging from DebugExceptions middleware becomes noise.
As an example, the application knows how to process ActionPolicy::Unauthorized, renders proper page in exceptions_app and thus the following log is redundant:
FATAL -- :
FATAL -- : ActionPolicy::Unauthorized (Not Authorized):
FATAL -- :
FATAL -- : app/controllers/topics_suggest_controller.rb:47:in `topic_load'
What would be the most idiomatic way to skip logging only those exceptions listed in rescue_responses?
Some historical notes are below. As of June 25, 2021, my PR to Rails https://github.com/rails/rails/pull/42592 has been accepted and this functionality will be available in Rails 6.1.5 7.0.0
Approach 1
It's tempting to delete DebugExceptions middleware from the application's Rails stack.
config/application.rb:
config.middleware.delete ActionDispatch::DebugExceptions
The problem is that it is exactly the middleware that determines Rails cannot find a route and throws ActionController::RoutingError in such cases. Hence, if you would like to react to this exception in your exceptions_app, this approach is a no-go for you.
Go ahead and use this approach if it's fine for you to see HTTP status 404 and plain text response Not found when no route was found.
Approach 2
Monkey patch or change ActionDispatch::DebugExceptions in some way. In fact, there was a proposal in 2013 to change DebugExceptions behavior so that it skips logging exceptions registered in rescue_responses: 9343, there is an example code in there.
I think it's too much to overwrite the whole class, so I chose to override its method log_error responsible for logging errors.
lib/ext/suppress_exceptions.rb:
module Ext
module SuppressExceptions
private
def log_error(_request, wrapper)
exception = wrapper.exception
return if ActionDispatch::ExceptionWrapper.rescue_responses.key? exception.class.name
super
end
end
end
config/initializers/error_handling.rb:
require_dependency 'ext/suppress_exceptions'
ActiveSupport.on_load(:action_controller) do
ActionDispatch::DebugExceptions.prepend Ext::SuppressExceptions
end
Related
I’m writing a plugin for a Rails application (Discourse) and setup routes like this:
Discourse::Application.routes.append do
root to: 'custom#show'
end
Unfortunately, the Rails application already defines a series of root routes in its routes.rb file. Since they’re specified first, they take precedence according to “Rails Routing from the Outside In: 2.2 CRUD, Verbs, and Actions”.
However, I noticed an odd logger entry when changing the route setup like this:
Discourse::Application.routes.prepend do
root to: 'custom#show'
end
By using prepend instead of append, Rails’ logger output now claims this when requesting the root path /:
INFO -- : Started GET "/" …
INFO -- : Processing by CustomController#show as HTML
However, the action CustomController#show is not actually called. The application behaves exactly as before. How can I get Rails to call this controller and action instead just like the logger claims?
(This is kind of a follow-up question to “For routes with identical URI patterns, which is matched first?”)
Probably some kind of infinite look in your before_actions / ApplicationController / or the inherited Discourse controller.
You can debug it with logging statements and Ctrl-C during the request to see where is the hang (the stacktrace will appear).
I am using the Apartment gem for a multi tenant Rails 5.2 app. I'm not sure that this even matters for my question but just giving some context.
Is there a way to override the Rails logger and redirect every single log entry to a file based on the tenant (database) that is being used?
Thinking... is there a method I can monkeypatch in Logger that will change the file written to dynamically?
Example: I want every error message directed to a file for that day. So at the end of a week there will be 7 dynamically generated files for errors that occurred on each specific day.
Another example: Before you write any server log message check if it is before 1pm. If it is before 1pm write it to /log/before_1.log ... if it is after 1pm write it to /log/after_1.log
Silly examples... but I want that kind of dynamic control before any line of log is written.
Thank you!
Usually the logger is usually configured per server (or per environment really) while apartment sets tenants per request - which means that in practice its not really going to work that well.
You can set the logger at any point by assigning Rails.logger to a logger instance.
Rails.logger = Logger.new(Rails.root.join('log/foo.log'), File::APPEND)
# or for multiple loggers
Rails.logger.extend(Logger.new(Rails.root.join('log/foo.log'), File::APPEND))
However its not that simple - you can't just throw that into ApplicationController and think that everything is hunky-dory - it will be called way to late and most of the entries with important stuff like the request or any errors that pop up before the controller will end up in the default log.
What you could do is write a custom piece of middleware that switches out the log:
# app/middleware/tenant_logger.rb
class TenantLogger
def initialize app
#app = app
end
def call(env)
file_name = "#{Appartment::Tenant.current}.log"
Rails.logger = Logger.new(Rails.root.join('log', file_name), File::APPEND)
#app.call(env)
end
end
And mount it after the "elevator" in the middleware stack:
Rails.application.config.middleware.insert_after Apartment::Elevators::Subdomain, TenantLogger
However as this is pretty far down in the middleware stack you will still miss quite a lot of important info logged by the middleware such as Rails::Rack::Logger.
Using the tagged logger as suggested by the Rails guides with a single file is a much better solution.
I have a piece of Rack middleware that loads a tenant, via subdomain, and applies some default settings. The middleware, while not pretty, does it's job fine enough. However, when an exception is thrown within the app the middleware "traps" the full stack trace. When I say trap I mean it hides the expected stack trace.
Here is an example.
I am throwing an exception in an a controller action like so:
def index
throw "Exception in a Rails controller action"
#taxonomies = Spree::Taxonomy.all
end
You would expect that the stack trace would reference this location but it does not. Instead it reference a line in the middleware.
Completed 500 Internal Server Error in 139ms
UncaughtThrowError (uncaught throw "Exception in a Rails controller action"):
lib/tenant_manager/middleware/loader.rb:42:in `call'
Why does this happen? Have you seen anything like this before?
Here is the middleware:
# lib/tenant_manager/middleware/loader.rb
module TenantManager
module Middleware
class Loader
# Middleware to detect an tenant via subdomain early in
# the request process
#
# Usage:
# # config/application.rb
# config.middleware.use TenantManager::Middleware::Loader
#
# A scaled down version of https://github.com/radar/houser
def initialize(app)
#app = app
end
def call(env)
domain_parts = env['HTTP_HOST'].split('.')
if domain_parts.length > 2
subdomain = domain_parts.first
tenant = Leafer::Tenant.find_by_database(subdomain)
if tenant
ENV['CURRENT_TENANT_ID'] = tenant.id.to_s
ENV['RAILS_CACHE_ID'] = tenant.database
Spree::Image.change_paths tenant.database
Apartment::Tenant.process(tenant.database) do
country = Spree::Country.find_by_name('United States')
Spree.config do |config|
config.default_country_id = country.id if country.present?
config.track_inventory_levels = false
end
Spree::Auth::Config.set(:registration_step => false)
end
end
else
ENV['CURRENT_TENANT_ID'] = nil
ENV['RAILS_CACHE_ID'] = ""
end
#app.call(env)
end
end
end
end
I am running ruby 2.2.0p0 and rails 4.1.8.
I have searched the webs for this but could not find anything, probably because I'm not serching for the right thing.
Any thoughts on why this is happening and what I am doing wrong?
Cheers!
I finally found the solution to this. It turns out that the last line in what is considered my application is in the middleware. I was running the rest of the code in a local rails engine located in a components directory. All we needed to do was create a new silencer for BacktraceCleaner. Notice components dir is now included.
# config/initializers/backtrace_silencers.rb
Rails.backtrace_cleaner.remove_silencers!
Rails.backtrace_cleaner.add_silencer { |line| line !~ /^\/(app|config|lib|test|components)/}
If you are interested here is an issue I posted on the rails project about how to replicate this in detail. https://github.com/rails/rails/issues/22265
Your middleware seems good. I think you have an issue with your backtrace_cleaner setting. Perhaps the cleaner gets overridden by a 3rd party gem. Try put a breakpoint (debugger) in the controller action method before the error raising, and print:
puts env['action_dispatch.backtrace_cleaner'].instance_variable_get(:#silencers).map(&:source_location).map{|l| l.join(':')}
to see the source locations of all the silencers which strip off non-app traces. By default it should only use Rails::BacktraceCleaner which locates at railties-4.1.8/lib/rails/backtrace_cleaner.rb
To directly see the silencer source code:
puts env['action_dispatch.backtrace_cleaner'].instance_variable_get(:#silencers).map{|s| RubyVM::InstructionSequence.disasm s }
See more from https://github.com/rails/rails/blob/master/railties/lib/rails/backtrace_cleaner.rb
https://github.com/rails/rails/blob/master/activesupport/lib/active_support/backtrace_cleaner.rb
You're not doing anything wrong. But a lot of middleware traps exceptions in order to do cleanup, including middleware that Rack inserts automatically in development mode. There is a specific Rack middleware inserted in development that will catch uncaught exceptions and give a reasonable HTML page instead of a raw stack dump (which you often won't see at all with common app servers.)
You can catch the exception yourself by putting a begin/rescue/end around your top level. Remember to catch "Exception", not just the default "rescue" with no arg, if you want to get everything. And you may want to re-throw the exception if you're going to leave this code in.
You can run in production mode -- that might stop the middleware from being inserted automatically by Rack.
You can find out what middleware is inserted (in Rails: "rake middleware") and then remove the middleware manually (in Rails "config.middleware.delete" or "config.middleware.disable").
There are probably other methods.
I have a small library, say widget_utils.rb, that lives in the lib/ directory of the app. (I've set the config to autoload source files from lib/)
The utils include the 'spira' gem which does ORM mapping based on RDF.rb. In the widget_utils.rb file are class objects based on my RDF repository, and they refer to a type called Spira::Types::Native.
I have a static method in WidgetUtils that returns a hash based on RDF data for use in rendering, WidgetUtils.options_for_select.
If I launch the console, I can call WidgetUtils.options_for_select and get back my hash perfectly.
But if I run the server, and try to render /widget/1234 or /widget/1234/edit to show one widget, I get the error Unrecognized type: Spira::Types::Native
At the bottom of my stack trace is widget_controller.rb, and at some point the haml file is doing a "load" of "lib/widget_utils.rb", and crashing with the Unrecognized type at the point where it's referenced in the util source file.
From the console if I do "load 'lib/widget_utils.rb'" I get no error, the type is recognized successfully.
I'm stumped, and too new to rails to successfully come up with a strategy to solve this problem outside of trial and error.
As it turns out, this problem is specific to the Spira library I'm working with, and JRuby when serving pages.
Spira keeps its collected known RDF types in a "settings" hash that it makes thread local. In most ordinary circumstances on MRI Ruby/Rails this isn't an issue, since the requests are usually handled in the same thread as the Spira initialization.
Similar problems will occur under JRuby if you attempt to make data global through, for instance, class variables. Some other mechanism needs to be found to make global reference data available to all service threads. (Mutable shared data is not even something to consider unless you like headaches.)
There is a workaround for Spira that will keep its settings available. In my utility class I added this monkey patch to take the settings out of Thread.local:
module Spira
def settings
#settings ||= {}
end
module_function :settings
end
I've figured out how to silence the contents of an action by wrapping everything inside the action's method in a logger.silence block.
However, I still get the call to the action showing up in the log file.
I.E:
Processing DashboardController#update (for 66.201.17.166 at 2009-09-09 19:03:27) [GET]
Parameters: {"todos"=>{"user"=>"25", "sfu_type"=>""}}
Completed in 1021ms (View: 439, DB: 438) | 200 OK [http://my.host.com/dashboard/update?todos%5Buser%5D=25&todos%5Bsfu_type%5D=]
I want to either keep the above from getting written to the logs all together, or redirect it to a different log file (i.e. dashboard.log) so it stops cluttering up the production.log file.
I get the above sample written to the log each time the ajax call updates for each user logged in. This updates about every 2 minutes so the production log file is getting flooded with unuseful log messages.
Late answer, but I spent quite a bit of time of the interwebs looking for the answer, which is thus:
In the controller that contains the action you would like to ignore, override the logger method and return the rails default for some actions, and a custom logger or nil for the ones that need special handling.
def logger
if params[:action] == 'action_to_ignore'
# return custom logger or nil
else
RAILS_DEFAULT_LOGGER
end
end
I suspect there is an easier way, and would love to hear it if it exists.
The logger calls are scattered all over ActionController::Base. The only way I can think of is to monkey patch a bunch of methods and check for the name of the controller in question.
Perhaps overriding this method is all you need to do. Not sure. Good luck :)
This looks like a really good, flexible solution using middleware:
http://dennisreimann.de/blog/silencing-the-rails-log-on-a-per-action-basis/
Have you set your production logging level?
config > environments > production.rb
config.log_level = :warn (default is :info)
In the application controller you could override the logger:
def log_error(exception)
#super(exception)
#do my logging here
end
I use this to email me when errors occur in a critical app.