【问题标题】:Rails: Catch all exceptions in a rails controllerRails:在 Rails 控制器中捕获所有异常
【发布时间】:2011-04-11 06:49:48
【问题描述】:

有没有办法在 Rails 控制器中捕获所有未捕获的异常,如下所示:

def delete
  schedule_id = params[:scheduleId]
  begin
    Schedules.delete(schedule_id)
  rescue ActiveRecord::RecordNotFound
    render :json => "record not found"
  rescue ActiveRecord::CatchAll
    #Only comes in here if nothing else catches the error
  end
  render :json => "ok"
end

谢谢

【问题讨论】:

    标签: ruby-on-rails


    【解决方案1】:

    你也可以定义一个rescue_from方法。

    class ApplicationController < ActionController::Base
      rescue_from ActionController::RoutingError, :with => :error_render_method
    
      def error_render_method
        respond_to do |type|
          type.xml { render :template => "errors/error_404", :status => 404 }
          type.all  { render :nothing => true, :status => 404 }
        end
        true
      end
    end
    

    根据您的目标,您可能还需要考虑不在每个控制器的基础上处理异常。相反,使用类似exception_handler gem 的东西来一致地管理对异常的响应。作为奖励,这种方法还将处理中间件层发生的异常,例如您的应用程序看不到的请求解析或数据库连接错误。 exception_notifier gem 可能也很有趣。

    【讨论】:

    • 这更加方便,因为它允许以 DRY 方式捕获异常。
    • 如果我使用没有参数的rescue_from?这会和救援一样吗?捕获所有错误?
    • rescue_from Exception 不是坏习惯吗?我的理解是最好从StandardError中解救出来,这样SyntaxErrorLoadError这样的东西就不会被抓到了。
    • 是的,拯救“异常”是不好的形式。请参阅 Avdi Grimm 的“Exceptional Ruby”,了解为什么会出现问题。
    【解决方案2】:
    begin
      # do something dodgy
    rescue ActiveRecord::RecordNotFound
      # handle not found error
    rescue ActiveRecord::ActiveRecordError
      # handle other ActiveRecord errors
    rescue # StandardError
      # handle most other errors
    rescue Exception
      # handle everything else
      raise
    end
    

    【讨论】:

    • 规则不是从不捕获异常吗?
    • 但我如何才能捕获 rescue =&gt; e 块中的所有类型?
    • @RonLugge 这完全取决于手头的情况。将“从不”作为经验法则是个坏主意。
    • @JustinSkiles 捕获异常将捕获语法错误(以及中断信号)。给我一个在生产代码中执行此操作的好方案。我可以直接捕获信号,但是您需要明确地这样做以明确您正在创建信号处理程序。只是捕捉异常......坏,坏主意。甚至可以捕捉到你不应该尝试捕捉的东西。
    • 从异常中拯救出来的少数常见情况之一是用于记录/报告目的,在这种情况下,您应该立即重新引发异常:stackoverflow.com/a/10048406/252346
    【解决方案3】:

    您可以按类型捕获异常:

    rescue_from ::ActiveRecord::RecordNotFound, with: :record_not_found
    rescue_from ::NameError, with: :error_occurred
    rescue_from ::ActionController::RoutingError, with: :error_occurred
    # Don't resuce from Exception as it will resuce from everything as mentioned here "http://stackoverflow.com/questions/10048173/why-is-it-bad-style-to-rescue-exception-e-in-ruby" Thanks for @Thibaut Barrère for mention that
    # rescue_from ::Exception, with: :error_occurred 
    
    protected
    
    def record_not_found(exception)
      render json: {error: exception.message}.to_json, status: 404
      return
    end
    
    def error_occurred(exception)
      render json: {error: exception.message}.to_json, status: 500
      return
    end
    

    【讨论】:

    【解决方案4】:

    rescue 不带参数将挽救任何错误。

    所以,你会想要:

    def delete
      schedule_id = params[:scheduleId]
      begin
        Schedules.delete(schedule_id)
      rescue ActiveRecord::RecordNotFound
        render :json => "record not found"
      rescue
        #Only comes in here if nothing else catches the error
      end
      render :json => "ok"
    end
    

    【讨论】:

    【解决方案5】:

    为了更好的用户体验而进行错误处理是一件非常困难的事情。

    在这里,我提供了一个完整的模板,让您的生活更轻松。这比 gem 更好,因为它完全可以为您的应用程序定制。

    注意:您可以随时在我的网站上查看此模板的最新版本:https://westonganger.com/posts/how-to-properly-implement-error-exception-handling-for-your-rails-controllers

    控制器

    class ApplicationController < ActiveRecord::Base
    
      def is_admin_path?
        request.path.split("/").reject{|x| x.blank?}.first == 'admin'
      end
    
      private
      
      def send_error_report(exception, sanitized_status_number)
        val = true
    
        # if sanitized_status_number == 404
        #   val = false
        # end
    
        # if exception.class == ActionController::InvalidAuthenticityToken
        #   val = false
        # end
    
        return val
      end
    
      def get_exception_status_number(exception)
        status_number = 500
    
        error_classes_404 = [
          ActiveRecord::RecordNotFound,
          ActionController::RoutingError,
        ]
    
        if error_classes_404.include?(exception.class)
          if current_user
            status_number = 500
          else
            status_number = 404
          end
        end
    
        return status_number.to_i
      end
    
      def perform_error_redirect(exception, error_message:)
        status_number = get_exception_status_number(exception)
    
        if send_error_report(exception, status_number)
          ExceptionNotifier.notify_exception(exception, data: {status: status_number})
        end
    
        ### Log Error
        logger.error exception
    
        exception.backtrace.each do |line| 
          logger.error line
        end
    
        if Rails.env.development?
          ### To allow for the our development debugging tools
          raise exception
        end
    
        ### Handle XHR Requests
        if (request.format.html? && request.xhr?)
          render template: "/errors/#{status_number}.html.erb", status: status_number
          return
        end
    
        if status_number == 404
          if request.format.html?
            if request.get?
              render template: "/errors/#{status_number}.html.erb", status: status_number
              return
            else
              redirect_to "/#{status_number}"
            end
          else
            head status_number
          end
    
          return
        end
    
        ### Determine URL
        if request.referrer.present?
          url = request.referrer
        else
          if current_user && is_admin_path? && request.path.gsub("/","") != admin_root_path.gsub("/","")
            url = admin_root_path
          elsif request.path != "/"
            url = "/"
          else
            if request.format.html?
              if request.get?
                render template: "/errors/500.html.erb", status: 500
              else
                redirect_to "/500"
              end
            else
              head 500
            end
    
            return
          end
        end
    
        flash_message = error_message
    
        ### Handle Redirect Based on Request Format
        if request.format.html?
          redirect_to url, alert: flash_message
        elsif request.format.js?
          flash[:alert] = flash_message
          flash.keep(:alert)
    
          render js: "window.location = '#{url}';"
        else
          head status_number
        end
      end
    
      rescue_from Exception do |exception|
        perform_error_redirect(exception, error_message: I18n.t('errors.system.general'))
      end
    
    end
    

    测试

    要在您的规范中对此进行测试,您可以使用以下模板:

    feature 'Error Handling', type: :controller do
    
      ### Create anonymous controller, the anonymous controller will inherit from stated controller
      controller(ApplicationController) do
        def raise_500
          raise Errors::InvalidBehaviour.new("foobar")
        end
    
        def raise_possible_404
          raise ActiveRecord::RecordNotFound
        end
      end
    
      before(:all) do
        @user = User.first
    
        @error_500 = I18n.t('errors.system.general')
        @error_404 = I18n.t('errors.system.not_found')
      end
    
      after(:all) do
        Rails.application.reload_routes!
      end
    
      before :each do
        ### draw routes required for non-CRUD actions
        routes.draw do
          get '/anonymous/raise_500'
          get '/anonymous/raise_possible_404'
        end
      end
    
      describe "General Errors" do
    
        context "Request Format: 'html'" do
          scenario 'xhr request' do
            get :raise_500, format: :html, xhr: true
            expect(response).to render_template('errors/500.html.erb')
          end
    
          scenario 'with referrer' do
            path = "/foobar"
    
            request.env["HTTP_REFERER"] = path
    
            get :raise_500
            expect(response).to redirect_to(path)
    
            post :raise_500
            expect(response).to redirect_to(path)
          end
    
          scenario 'admin sub page' do
            sign_in @user
    
            request.path_info = "/admin/foobar"
    
            get :raise_500
            expect(response).to redirect_to(admin_root_path)
    
            post :raise_500
            expect(response).to redirect_to(admin_root_path)
          end
    
          scenario "admin root" do
            sign_in @user
    
            request.path_info = "/admin"
    
            get :raise_500
            expect(response).to redirect_to("/")
    
            post :raise_500
            expect(response).to redirect_to("/")
          end
    
          scenario 'public sub-page' do
            get :raise_500
            expect(response).to redirect_to("/")
    
            post :raise_500
            expect(response).to redirect_to("/")
          end
    
          scenario 'public root' do
            request.path_info = "/"
    
            get :raise_500
            expect(response).to render_template('errors/500.html.erb')
            expect(response).to have_http_status(500)
    
            post :raise_500
            expect(response).to redirect_to("/500")
          end
    
          scenario '404 error' do
            get :raise_possible_404
            expect(response).to render_template('errors/404.html.erb')
            expect(response).to have_http_status(404)
    
            post :raise_possible_404
            expect(response).to redirect_to('/404')
    
            sign_in @user
    
            get :raise_possible_404
            expect(response).to redirect_to('/')
    
            post :raise_possible_404
            expect(response).to redirect_to('/')
          end
        end
    
        context "Request Format: 'js'" do
          render_views ### Enable this to actually render views if you need to validate contents
          
          scenario 'xhr request' do
            get :raise_500, format: :js, xhr: true
            expect(response.body).to include("window.location = '/';")
    
            post :raise_500, format: :js, xhr: true
            expect(response.body).to include("window.location = '/';")
          end
    
          scenario 'with referrer' do
            path = "/foobar"
    
            request.env["HTTP_REFERER"] = path
    
            get :raise_500, format: :js
            expect(response.body).to include("window.location = '#{path}';")
    
            post :raise_500, format: :js
            expect(response.body).to include("window.location = '#{path}';")
          end
    
          scenario 'admin sub page' do
            sign_in @user
    
            request.path_info = "/admin/foobar"
    
            get :raise_500, format: :js
            expect(response.body).to include("window.location = '#{admin_root_path}';")
    
            post :raise_500, format: :js
            expect(response.body).to include("window.location = '#{admin_root_path}';")
          end
    
          scenario "admin root" do
            sign_in @user
    
            request.path_info = "/admin"
    
            get :raise_500, format: :js
            expect(response.body).to include("window.location = '/';")
    
            post :raise_500, format: :js
            expect(response.body).to include("window.location = '/';")
          end
    
          scenario 'public page' do
            get :raise_500, format: :js
            expect(response.body).to include("window.location = '/';")
    
            post :raise_500, format: :js
            expect(response.body).to include("window.location = '/';")
          end
    
          scenario 'public root' do
            request.path_info = "/"
    
            get :raise_500, format: :js
            expect(response).to have_http_status(500)
    
            post :raise_500, format: :js
            expect(response).to have_http_status(500)
          end
    
          scenario '404 error' do
            get :raise_possible_404, format: :js
            expect(response).to have_http_status(404)
    
            post :raise_possible_404, format: :js
            expect(response).to have_http_status(404)
    
            sign_in @user
    
            get :raise_possible_404, format: :js
            expect(response).to have_http_status(200)
            expect(response.body).to include("window.location = '/';")
    
            post :raise_possible_404, format: :js
            expect(response).to have_http_status(200)
            expect(response.body).to include("window.location = '/';")
          end
        end
    
        context "Other Request Format" do
          scenario '500 error' do
            get :raise_500, format: :json
            expect(response).to have_http_status(500)
    
            post :raise_500, format: :json
            expect(response).to have_http_status(500)
          end
          
          scenario '404 error' do
            get :raise_possible_404, format: :json
            expect(response).to have_http_status(404)
    
            post :raise_possible_404, format: :json
            expect(response).to have_http_status(404)
    
            sign_in @user
    
            get :raise_possible_404, format: :json
            expect(response).to have_http_status(500)
    
            post :raise_possible_404, format: :json
            expect(response).to have_http_status(500)
          end
        end
    
      end
    
    end
    

    【讨论】:

      【解决方案6】:

      实际上,如果你真的想捕捉所有东西,你只需创建自己的异常应用程序,它可以让你自定义通常由 PublicExceptions 中间件处理的行为:https://github.com/rails/rails/blob/4-2-stable/actionpack/lib/action_dispatch/middleware/public_exceptions.rb

      许多其他答案分享了可以为您做到这一点的宝石,但您真的没有理由不能只看它们并自己做。

      一个警告:确保您永远不会在您的异常处理程序中引发异常。否则你会得到一个丑陋的 FAILSAFE_RESPONSE https://github.com/rails/rails/blob/4-2-stable/actionpack/lib/action_dispatch/middleware/show_exceptions.rb#L4-L22

      顺便说一句,控制器中的行为来自可恢复的:https://github.com/rails/rails/blob/4-2-stable/activesupport/lib/active_support/rescuable.rb#L32-L51

      【讨论】:

        猜你喜欢
        • 2014-05-21
        • 1970-01-01
        • 2012-06-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-31
        相关资源
        最近更新 更多