Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Railsの開発環境で表示される派手な例外ページの仕組みを徹底解説

Railsで開発をしていて何かがうまくいかないとき、とても見やすいエラー詳細ページが表示されるのは嬉しいところです。バックトレースにはアプリに関連する部分がハイライトされ、POSTされたパラメータを確認したり、環境変数やセッション変数を調査することもできます。

今回は、こうした洗練されたエラーページがどのように動作しているのかを詳しく見ていきます。

Railsの開発環境で表示される派手な例外ページの仕組みを徹底解説

ActionPackの中身を覗いてみる

今回主に扱うファイルは actionpack/lib/action_dispatch/middleware/debug_exceptions.rb です。開発モードのエラーページ表示において、最も重要な役割を担っているのがこのファイルです。ちなみに、本番モードのエラー画面がどこから来ているのか気になる方は、public_exceptions.rb を参照してみてください。

Rackミドルウェアとは

Rackミドルウェアに馴染みがない方のために簡単に説明すると、その概念はとてもシンプルです。Rackミドルウェアを使うと、HTTPリクエストがアプリに届く前に介入したり、逆にアプリの出力がユーザーへ返される前に横取りしたりすることができます。

以下は、特に何もしないごくシンプルなミドルウェアの例です。

class MyMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    @app.call(env)
  end
end

Rackミドルウェアですべての例外を捕捉する

アプリ内で発生するすべての例外は、@app.call() の呼び出し結果として発生します。つまり、Rackアプリ内のすべての例外を捕捉するのは、ミドルウェアに rescue 節を追加するだけで実現できるのです。

def call(env)
  @app.call(env)
rescue StandardError => exception
  # これは例外ページを生成するために自分で用意する必要のあるメソッド
  render_exception(env, exception)
end

call メソッドから返されたものは、あたかも通常のWebページであるかのように扱われます。したがって、render_exception が返すコンテンツが、元のレスポンスを置き換えることになります。

例外ページの描画処理

以下は、ActionDispatch::DebugExceptions から抜粋した render_exception メソッドです。ご覧のとおり、例外から必要なデータを取り出し、それをERBテンプレートに渡して描画しているだけという、いたってシンプルな作りになっています。

def render_exception(env, exception)
  wrapper = ExceptionWrapper.new(env, exception)
  log_error(env, wrapper)

  if env['action_dispatch.show_detailed_exceptions']
    request = Request.new(env)
    template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
      request: request,
      exception: wrapper.exception,
      application_trace: wrapper.application_trace,
      framework_trace: wrapper.framework_trace,
      full_trace: wrapper.full_trace,
      routes_inspector: routes_inspector(exception),
      source_extract: wrapper.source_extract,
      line_number: wrapper.line_number,
      file: wrapper.file
    )
    file = "rescues/#{wrapper.rescue_template}"

    if request.xhr?
      body = template.render(template: file, layout: false, formats: [:text])
      format = "text/plain"
    else
      body = template.render(template: file, layout: 'rescues/layout')
      format = "text/html"
    end
    render(wrapper.status_code, body, format)
  else
    raise exception
  end
end

def render(status, body, format)
  [status, {'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
end

その他の活用例

このRackミドルウェアのテクニックを応用すれば、例外を使ったさまざまな興味深いことが実現できます。私たちHoneybadgerでは、この仕組みを利用してエラーを横取りし、APIに記録しています。以下はそのために実際に使っているコードです。

def call(env)
  config.with_request(::Rack::Request.new(env)) do
    begin
      env['honeybadger.config'] = config
      response = @app.call(env)
    rescue Exception => raised
      env['honeybadger.error_id'] = notify_honeybadger(raised, env)
      raise
    end

    framework_exception = framework_exception(env)
    if framework_exception
      env['honeybadger.error_id'] = notify_honeybadger(framework_exception, env)
    end

    response
  end
ensure
  Honeybadger.context.clear!
end


  1. 【Rails】Flashメッセージの使い方を徹底解説!alertとnoticeの違いからflash.nowまで

    Flashメッセージとは? Flashメッセージとは、Railsアプリケーションのユーザーに対して、自分の操作によって何が起こったのかを伝えるための仕組みです。 メッセージの例: 「パスワードが正しく変更されました」(確認メッセージ) 「ユーザーが見つかりません」(エラーメッセージ) Flashメッセージはコントローラーで設定し、ビューで表示します。ユーザーはそのメッセージを受け取って、次のアクションにつなげられます。 それでは、具体的な使い方を見ていきましょう! Flashメッセージの基本的な使い方 通知メッセージは、flashヘルパーメソッドを使って扱います。 flashオブジェクトは

  2. RailsアプリでTwitterログインを実装する方法:OmniAuth-Twitter完全ガイド

    このチュートリアルでは、RailsアプリケーションのユーザーがTwitterアカウントを使ってログインできるようにする方法を解説します。OAuthなどのツールのおかげで、この実装はとても簡単に行えるようになりました。 今回は、OmniAuth用のTwitterストラテジーを含む「OmniAuth-Twitter」を使用します。 それでは始めましょう! はじめに:プロジェクトの準備 まずはRailsアプリケーションを生成します。ターミナルで以下のコマンドを実行してください。 rails new Tuts-Social -T 次にGemfileを開き、bootstrap gemを追加します。