RailsDFW
RailsDFW

Reputation: 1573

How to classify ActionController::RoutingError as an error in Sentry

I am using the new Sentry (not Raven) for Ruby for my Rails 6.1 application:

gem "sentry-ruby"
gem "sentry-rails"

I am able to see the transactions when users triggered ActionController::RoutingError, but I want these to appear as errors in Sentry. I do not see ActionController::RoutingError as an 'ignored' or 'excluded' error:

> Sentry::Configuration::IGNORE_DEFAULT
 => ["Mongoid::Errors::DocumentNotFound", "Rack::QueryParser::InvalidParameterError", "Rack::QueryParser::ParameterTypeError", "Sinatra::NotFound"] 

I tried clearing excluded_exceptions in sentry.rb initializer file, but this had no effect:

Sentry.init do |config|
  ...
  config.excluded_exceptions = []
end

How can I configure Sentry so that these are sent as errors? I was also thinking that I can modify the middleware (ActionDispatch?, ActionPack?) and add Sentry.capture_message where appropriate, but I am not sure how to do that.

I do not want a "catch-all" route that redirects to an ErrorController or ApplicationController:

match '*path', to: "application#handle_route_error", via: :all

Upvotes: 3

Views: 404

Answers (2)

Alex
Alex

Reputation: 30036

I don't use sentry, just replace p with whatever command for sentry:

Rescue

For production only, this is required: config.consider_all_requests_local = false.

# config/initializers/report_routing_error.rb

class ReportRoutingErrorMiddleware
  def initialize app
    @app = app
  end

  def call(env)
    @app.call(env)
  rescue ActionController::RoutingError => e
    p ["NOT FOUND", e]
    raise
  end
end

# DebugExceptions raises RoutingError when consider_all_requests_local is false
# insert_before - to catch the error
Rails.application.config.middleware.insert_before ActionDispatch::DebugExceptions, ReportRoutingErrorMiddleware

#=> ["NOT FOUND", #<ActionController::RoutingError: No route matches [GET] "/asdf">]

404

In development, DebugExceptions middleware technically raises the error but it also rescues it and renders the full error page. Until response gets to that middleware it is just a 404 response from the router: https://github.com/rails/rails/blob/v7.0.7/actionpack/lib/action_dispatch/routing/route_set.rb#L37

# config/initializers/report_routing_error.rb

class ReportRoutingErrorMiddleware
  def initialize app
    @app = app
  end

  def call(env)
    status, headers, body = response = @app.call(env)
    if status == 404
      # no error, so get what you need from env
      p ["NOT FOUND", env["REQUEST_METHOD"], env["PATH_INFO"]]
    end
    response
  end
end

# insert_after - to get response first, because it doesn't go past DebugExceptions
Rails.application.config.middleware.insert_after ActionDispatch::DebugExceptions, ReportRoutingErrorMiddleware

#=> ["NOT FOUND", "GET", "/asdf"]

Intercept

Apparently, they've figured it out a long time ago register_interceptor:

# config/initializers/report_routing_error.rb

ActionDispatch::DebugExceptions.register_interceptor do |req, exception|
  if exception.is_a? ActionController::RoutingError
    p ["NOT FOUND", req, exception]
  end
end

#=> ["NOT FOUND", #<ActionDispatch::Request GET "http://0.0.0.0:3000/asdf" for 127.0.0.1>, #<ActionController::RoutingError: No route matches [GET] "/asdf">]

Introduce ActionDispatch::DebugExceptions.register_interceptor, a way to hook into DebugExceptions and process the exception, before being rendered.

https://github.com/rails/rails/pull/23868

Upvotes: 3

mechnicov
mechnicov

Reputation: 15308

The problem is that this error is handled by default in Rails and rescued with "Not Found" response

Of course you can handle such errors ("Not Found", "Unprocessable Entity", "Internal Server Error", etc.) manually using router and response with specific action of something like ErrorsController. You can send message to Sentry from there

But since you don't want such decision, you can monkeypatch middleware or even exactly ActionController::RoutingError. Find source of this error and add initializer like this

# config/initializers/handle_routing_error.rb

module ActionController
  class RoutingError < ActionControllerError
    attr_reader :failures
    def initialize(message, failures = [])
      Sentry.capture_message(message) # or Sentry.capture_error(self)

      super(message)
      @failures = failures
    end
  end
end

Usually monkeypatch is some hack and not nice solution, but it will work

Upvotes: 2

Related Questions