Railsのレスポンス遅延を通知する
この記事はRuby on Rails Advent Calendar 2015の13日目です。
Active Support Instrumentationとは
ざっくり言うと「Railsアプリケーションやフレームワーク内のアクションを計測するためのAPI」です。
この仕組みを使うと、コントローラのアクション実行やSQL実行などをフックすることができます。
今回はこのInstrumentationの仕組みを使ってレスポンス遅延の監視を行います。
アクションを監視する
アクションを監視するには提供されているフックをsubscribeします。
アクションのフックはprocess_action.action_controllerという名前なので
# config/initializers/subscribe_process_action_action_controller.rb
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
endという感じのファイルを用意するとアクションを監視できます。
レスポンス遅延の場合に通知する
subscribeした時のブロック引数にレスポンス情報が入っているので、レスポンス時間が長過ぎる場合に通知をしてみます。
ブロック引数はそのままActiveSupport::Notifications::Event.newの引数になるので、まずはActiveSupport::Notifications::Eventオブジェクトを作成します。
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
  event = ActiveSupport::Notifications::Event.new(*args)
endeventオブジェクトを取得したら、event.durationで取得できるレスポンス時間(単位はミリ秒)が10秒以上かかっているか判定します。
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
  event = ActiveSupport::Notifications::Event.new(*args)
  if event.duration > 10_000
    ## ここで通知処理
  end
endメール/チャットで通知
あとは通知処理をすればOKです。
ただし、フック処理が終わるまでアクションが終了しないので、通知処理で邪魔しないように非同期で通知するようにしましょう。
例えばResqueを使っている場合は
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
  event = ActiveSupport::Notifications::Event.new(*args)
  if event.duration > 10_000
    Resque.enqueue(
      SlowResponseNotifyJob,
      time: event.time,
      duration: "#{(event.duration / 1000).round(3)}sec"}.merge(event.payload)
    )
  end
endという感じにすると良いかと思います。
あとはSlowResponseNotifyJobクラスを実装してメールなりSlackなりHipchatなりに通知するようにしましょう。
おまけ:スロークエリの通知
Instrumentation機能を使うとSQL実行遅延の通知もできます。
SQL実行のhook名はsql.active_recordなので
# config/initializer/subscribe_sql_active_record.rb
ActiveSupport::Notifications.subscribe "sql.active_record" do |*args|
  event = ActiveSupport::Notifications::Event.new(*args)
  if event.duration > 10_000
    Resque.enqueue(
      SlowQueryNotifyJob,
      time: event.time,
      duration: "#{(event.duration / 1000).round(3)}sec"}.merge(event.payload)
    )
  end
endという感じにすればOKです。
他にも色々なフックがあるのでActive Support Intrumentationのドキュメントを参考に、必要な監視を設定してみてください。
