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)
end

eventオブジェクトを取得したら、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のドキュメントを参考に、必要な監視を設定してみてください。