1 分鐘閱讀

本篇接續上一篇介紹Rails Instrumentation API的文章,如果不清楚或忘記Active Support Instrumentation是做什麼的歡迎回顧上一篇文章。

上篇文末介紹了Rails有提供了許多的事件可以使用,主要是用來產生log(或有其他用途但我沒深入研究),例如我們常常在Rails Console中下某個query後,會在console出現下面的訊息:

sql.active_record事件

這個訊息就是來自Rails內建的sql.active_record事件。當我們透過ActiveRecor向資料庫送出請求時,會送出一個sql.active_record事件,而在某處有個Subscriber會處理該事件會產生log。今天就是要來把這個Subscriber找出來他到底藏在Rails的哪個角落!

Active Support Subscriber

Rails Guide中特別的頁面在沒有特別介紹他在哪裡設定了Subscriber,因此我到了Rails Guide的API去找答案,找到了ClassActiveSupport::Subscriber在負責訂閱事件。根據官方文件描述這個Class的用途:

引用自Rails Guide API

ActiveSupport::Subscriber is an object set to consume ActiveSupport::Notifications. The subscriber dispatches notifications to a registered object based on its given namespace.

An example would be an Active Record subscriber responsible for collecting statistics about queries:

1
2
3
4
5
6
7
8
9
module ActiveRecord
  class StatsSubscriber < ActiveSupport::Subscriber
    attach_to :active_record

    def sql(event)
      Statsd.timing("sql.#{event.payload[:name]}", event.duration)
    end
  end
end

After configured, whenever a “sql.active_record” notification is published, it will properly dispatch the event (ActiveSupport::Notifications::Event) to the sql method.

We can detach a subscriber as well:

1
ActiveRecord::StatsSubscriber.detach_from(:active_record)

簡單翻譯如下:

ActiveSupport::Subscriber是個用來處理ActiveSupport::Notifications的物件。Subscriber會根據namespace將事件的通知傳遞到註冊事件的物件。

範例:Active Record的subscriber中負責收集ActivceRecord執行query時的資訊:

example code showed before

當subscriber設置好後,每當sql.active_support事件發出通知時,會自動將事件(ActiveSupport::Notifications::Event)傳遞到sql方法。

也可以將subscriber與已經對接的事件分離。

example code showed before

由此可知,原本是透過ActiveSupport::Notification.subscribe來訂閱事件,現在可以利用ActiveSupport::Subscriber來訂閱事件。所謂的namespace則是依照原本事件的naming來進行設置。ActiveSupport::Subscriber中定義了兩個的class method:

  1. attach_to: 負責將library發出的事件對接到subscriber,參數的type為symbol,其value為事件的naming convention中library的部分。
  2. detach_from: 負責將已經對接的subscriber分離,參數的設置與attach_from一樣。

naming convention中event的部分,則需要我們自己定義instance method,其名稱與event的名稱相同,這麼一來當事件發出通知時,subscriber會將通知分配到該instance method。因此原本定義在ActiveSupport::Notification.subscribe的block內的程式碼就變成寫在instance method內。

有個設置subscriber的base class(ActiveSupport::Subscriber)後,我們可以透過繼承這個class來設定subscriber,如果剛剛有注意到前面範例的程式碼的話便可以發現,實際上Rails也是這麼做的!Rails有個專門用於產生log的Subscriber-ActiveSupport::LogSubscriber,範例裡面ClassActiveRecord::StateSubscriber繼承自ActiveSupport::LogSubscriber,而ActiveSupport::LogSubscriber繼承自ActiveSupport::Subscriber

Lograge

接下來來介紹一個Gem-Lograge,這個Gem可以將原本Rails中ActionController以及ActionCable的log有些多餘的資訊刪掉,讓我們利用log來debug時,更容易搜索或閱讀log。今天就來翻翻source code來看看它是怎麼辦到的!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# /lib/lograge.rb
module Lograge
  def remove_existing_log_subscriptions
    ActiveSupport::LogSubscriber.log_subscribers.each do |subscriber|
      case subscriber
      when ActionView::LogSubscriber
        unsubscribe(:action_view, subscriber)
      when ActionController::LogSubscriber
        unsubscribe(:action_controller, subscriber)
      end
    end
  end

  def unsubscribe(component, subscriber)
    events = subscriber.public_methods(false).reject { |method| method.to_s == 'call' }
    events.each do |event|
      ActiveSupport::Notifications.notifier.listeners_for("#{event}.#{component}").each do |listener|
        ActiveSupport::Notifications.unsubscribe listener if listener.instance_variable_get('@delegate') == subscriber
      end
    end
  end

  def attach_to_action_controller
    Lograge::LogSubscribers::ActionController.attach_to :action_controller
  end
end

/lib/lograge.rb這個檔案內,可以看到Lograge定義了remove_existing_log_subscriptions方法,用ActiveSupport::LogSubscriber.log_subscribers找出所有的subscriber,並且取消了將這兩個subscriber訂閱的事件。也定義了attach_to_action_controller方法,將Lograge::LogSubscribers::ActionController對接到事件名稱library的部分是action_controller內的事件。

Lograge自己建立了一個ClassLograge::LogSubscribers::ActionController來代替原本的ActiveSupport::LogSubscriber,所以接下來來看看這檔案/lib/lograge/log_subscribers/action_controller.rb定義了哪些東西。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module Lograge
  module LogSubscribers
    class ActionController < Base
    def process_action(event)
      process_main_event(event)
    end

    def redirect_to(event)
      RequestStore.store[:lograge_location] = event.payload[:location]
    end

    def unpermitted_parameters(event)
      RequestStore.store[:lograge_unpermitted_params] ||= []
      RequestStore.store[:lograge_unpermitted_params].concat(event.payload[:keys])
    end
  end
end

在這個class內,定義了三個instance method,process_actionredirect_tounpermitted_parameters,而這三個方法的目的就是要處理事件名稱是process_action.action_controllerredirect_to.action_controllerunpermitted_parameters.action_controller的事件!而這三個事件也就是上一篇文末提的,Rails原本就提供有關於ActionController的事件。


Rails Instrumentation API的發布事件還有訂閱事件的介紹就到這邊。這次工作上接到的任務讓我又學到了Rails的其中一種黑魔法,而且也追了兩個Gem的source code來看看他們是怎麼透過Rails Instrumentation API以及Active Support Subscriber來實作產生log message。


此篇文主要目的在記錄學習程式語言時的筆記,如果有錯誤之處請不吝於留言給予指教,我會很感謝您給的回饋! 以上內容參考自下列網站/文章:

  1. Rails Guide API
  2. Lograge

留言