RailsでリクエストをWebサーバーから受け取った直後にBreakpointを貼ってデバッグする

RailsでPumaの挙動を確認したかったのだがデバッグするのにRackのMiddlewareが邪魔だったので回避策を検討してみた。

環境

詳細

Railsから見て先頭付近でリクエストを受け付けるところとして最初にApplicationControllerにデバッガを貼ってみたがBacktraceを見ると60行ほどのスタックでPumaからBreakpointまで結構な量のMiddlewareを通過していた。

class ApplicationController < ActionController::Base
    binding.break
end

rails middleware を打つとnewしたてのRailsアプリでもこれだけのMIddlewareが表示される。これをいちいち遡ってPumaまでたどりくのは面倒だ。

use ActionDispatch::HostAuthorization
use Rack::Sendfile
use ActionDispatch::Static
use ActionDispatch::Executor
use ActionDispatch::ServerTiming
use ActiveSupport::Cache::Strategy::LocalCache::Middleware
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use ActionDispatch::RemoteIp
use Sprockets::Rails::QuietAssets
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use WebConsole::Middleware
use ActionDispatch::DebugExceptions
use ActionDispatch::ActionableExceptions
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ContentSecurityPolicy::Middleware
use ActionDispatch::PermissionsPolicy::Middleware
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
use Rack::TempfileReaper
run MyApp::Application.routes

アプリケーションとしてのRailsにリクエストが届くのがMiddlewareを経過した後なら、Middlewareの中にBreakpointを貼って然るべき場所に登録してやれば良いだろうという事で↓のようなただリクエストを通過させるMiddlewareを作成してBreakpointを設定。

class MyMiddleware
    def initialize(app)
        @app = app
    end
    
    def call(env)
        status, headers, body = @app.call(env)
        binding.break
        [status, headers, body]
    end
end

このMiddlewareをconfig/application.rbから登録すれば良いのだがconfig.middleware.useを使うとMiddlewareの最後、アプリケーションの直前に挿入されてしまうのでconfig.middleware.insert_beforeを使って一番上のMiddlewareの前に登録してやる。

config.middleware.insert_before ActionDispatch::HostAuthorization, MyMiddleware
$ rails middleware
use MyApp::Application::MyMiddleware
use ActionDispatch::HostAuthorization
use Rack::Sendfile
...
run MyApp::Application.routes

この状態でRailsのseverを起動して適当なリクエストを送ると先頭に追加したMiddlewareの中でデバッガが実行されてPumaの直後ぐらいにBreakpointが止まってくれる。あとは少しスタックを昇っていけばさほど労せずに目的のPumaまでたどり着ける。

$ backtrace
#0 MyApp::Application::MyMiddleware#call(env={"rack.version"=>[1, 6], "rack.errors"=>...)
#1 Rails::Engine#call(env={"rack.version"=>[1, 6], "rack.errors"=>...)
#2 Puma::Configuration::ConfigMiddleware#call(env={"rack.version"=>[1, 6], "rack.errors"=>...)

感想

軽い気持ちでPumaの挙動を確認しようと思ったらMiddlewareが結構邪魔で、当初回避方法を思いつかずInitializerとして登録したりして右往左往したので同じ轍を踏まないように記録しておく。 最終的にはシンプルな形で解決出来たのでPumaに限らずinsert_beforeの対象を適当なMiddlewareにすれば実際に開発しているアプリでMiddlewareのデバッグもしやすくなったので結果オーライか。