rails 源码解析之log notification/subscribe

我调试rails代码的习惯是边看代码边看日志,通过日志你可以看到每一处rails对DB的操作。这在web开发看来还是挺简单,只有在少数情况下才去使用pry或者debugger去调试代码。一来直接看日志就大致上可以看到你写的代码执行的逻辑,比如执行insert语句的时候你大概也能猜到应该是调用了 Model.create 方法。直接看日志这种方式比较便捷。但是默认rails是把所有的sql都显示在日志里面的,实际上我们关心的无非是insert/update/delete语句,至于select基本上不太关心了。但是项目逻辑一旦复杂,一个请求有上百条sql的时候,就不太容易找到insert/update/delete了。所以制作了一个gem,用来高亮insert/update/delete语句。自我感觉还不错,可以在github上找到(https://github.com/xumc/simple-debug-for-rails/tree/v1.0.0)。目前还比较简陋,以后慢慢完善。见图:

gem的事情暂且不表,下面讲讲rails中的log 相关代码。这也是为了写这个gem才看这部分代码。

涉及到log相关的rb文件如下图:

rails caicai$ tree -C -f|grep "subscribe\|notifications"

│   │   │   ├── ./actionmailer/lib/action_mailer/log_subscriber.rb

│       ├── ./actionmailer/test/log_subscriber_test.rb

│   │   │   ├── ./actionpack/lib/action_controller/log_subscriber.rb

│       │   ├── ./actionpack/test/controller/log_subscriber_test.rb

│   │   │   ├── ./actionview/lib/action_view/log_subscriber.rb

│       │   ├── ./actionview/test/template/log_subscriber_test.rb

│   │   │   ├── ./activerecord/lib/active_record/explain_subscriber.rb

│   │   │   ├── ./activerecord/lib/active_record/log_subscriber.rb

│       │   ├── ./activerecord/test/cases/explain_subscriber_test.rb

│       │   ├── ./activerecord/test/cases/log_subscriber_test.rb

│       │   ├── ./activerecord/test/fixtures/subscribers.yml

│       │   ├── ./activerecord/test/models/subscriber.rb

│   │   │   ├── ./activesupport/lib/active_support/log_subscriber

│   │   │   │   └── ./activesupport/lib/active_support/log_subscriber/test_helper.rb

│   │   │   ├── ./activesupport/lib/active_support/log_subscriber.rb

│   │   │   ├── ./activesupport/lib/active_support/notifications

│   │   │   │   ├── ./activesupport/lib/active_support/notifications/fanout.rb

│   │   │   │   └── ./activesupport/lib/active_support/notifications/instrumenter.rb

│   │   │   ├── ./activesupport/lib/active_support/notifications.rb

│   │   │   ├── ./activesupport/lib/active_support/subscriber.rb

│       ├── ./activesupport/test/log_subscriber_test.rb

│       ├── ./activesupport/test/notifications

│       │   ├── ./activesupport/test/notifications/evented_notification_test.rb

│       │   └── ./activesupport/test/notifications/instrumenter_test.rb

│       ├── ./activesupport/test/notifications_test.rb

│       ├── ./activesupport/test/subscriber_test.rb

│       │   │   └── ./railties/test/application/initializers/notifications_test.rb

很明显,rails会使用pub/sub方式记录日志。类似于消息队列的意思。 比如执行sql的时候会生成sql事件, log订阅者则能够接受到这些sql事件,并且把相关的信息记录到log中。这样就避免了在代码里面直接写logger.debug/info/error 等语句。将log和其他组件进行解耦。从上面的文件树可以看出rails的各大组件均有一个log_subscriber.rb文件。里面的代码便是捕获组件内部产生的event并且将event信息写到log文件的代码。

以/activerecord/lib/active_record/log_subscriber.rb为例:

module ActiveRecord
  class LogSubscriber < ActiveSupport::LogSubscriber
    IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]

    def self.runtime=(value)
      ActiveRecord::RuntimeRegistry.sql_runtime = value
    end

    def self.runtime
      ActiveRecord::RuntimeRegistry.sql_runtime ||= 0
    end

    def self.reset_runtime
      rt, self.runtime = runtime, 0
      rt
    end

    def initialize
      super
      @odd = false
    end

    def render_bind(column, value)
      if column
        if column.binary?
          # This specifically deals with the PG adapter that casts bytea columns into a Hash.
          value = value[:value] if value.is_a?(Hash)
          value = value ? "<#{value.bytesize} bytes of binary data>" : "<NULL binary data>"
        end

        [column.name, value]
      else
        [nil, value]
      end
    end

    def sql(event)
      self.class.runtime += event.duration
      return unless logger.debug?

      payload = event.payload

      return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])

      name  = "#{payload[:name]} (#{event.duration.round(1)}ms)"
      sql   = payload[:sql]
      binds = nil

      unless (payload[:binds] || []).empty?
        binds = "  " + payload[:binds].map { |col,v|
          render_bind(col, v)
        }.inspect
      end

      if odd?
        name = color(name, CYAN, true)
        sql  = color(sql, nil, true)
      else
        name = color(name, MAGENTA, true)
      end

      debug "  #{name}  #{sql}#{binds}"
    end

    def odd?
      @odd = [email protected]
    end

    def logger
      ActiveRecord::Base.logger
    end
  end
end

ActiveRecord::LogSubscriber.attach_to :active_record

这个类继承自ActiveSupport::LogSubscriber这个类,联系到上面的文件结构,可以猜出ActiveSupport::LogSubscriber应该是rails各大组件中的LogSubscriber的基类。active_record中的这个类主要的函数sql(event)将event中的信息转换成伟log文本调用debug方式存储到log中。 文件最后一句使用 attach_to 意为用这个类来监听active_record产生的事件。

再来看看ActiveSupport::LogSubscriber这个类:

module ActiveSupport
 class LogSubscriber < Subscriber
    # Embed in a String to clear all previous ANSI sequences.
    CLEAR   = "\e[0m"
    BOLD    = "\e[1m"

    # Colors
    BLACK   = "\e[30m"
    RED     = "\e[31m"
    GREEN   = "\e[32m"
    YELLOW  = "\e[33m"
    BLUE    = "\e[34m"
    MAGENTA = "\e[35m"
    CYAN    = "\e[36m"
    WHITE   = "\e[37m"

    mattr_accessor :colorize_logging
    self.colorize_logging = true

    class << self
      def logger
        @logger ||= if defined?(Rails) && Rails.respond_to?(:logger)
          Rails.logger
        end
      end

      attr_writer :logger

      def log_subscribers
        subscribers
      end

      # Flush all log_subscribers' logger.
      def flush_all!
        logger.flush if logger.respond_to?(:flush)
      end
    end

    def logger
      LogSubscriber.logger
    end

    def start(name, id, payload)
      super if logger
    end

    def finish(name, id, payload)
      super if logger
    rescue Exception => e
      logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
    end

  protected

    %w(info debug warn error fatal unknown).each do |level|
      class_eval <<-METHOD, __FILE__, __LINE__ + 1
        def #{level}(progname = nil, &block)
          logger.#{level}(progname, &block) if logger
        end
      METHOD
    end

    # Set color by using a string or one of the defined constants. If a third
    # option is set to +true+, it also adds bold to the string. This is based
    # on the Highline implementation and will automatically append CLEAR to the
    # end of the returned String.
    def color(text, color, bold=false)
      return text unless colorize_logging
      color = self.class.const_get(color.upcase) if color.is_a?(Symbol)
      bold  = bold ? BOLD : ""
      "#{bold}#{color}#{text}#{CLEAR}"
    end
  end
end

其方法还算中规中矩的辅助方法, 这是这个类又继承自subscribe类,这里很明显了,这个subscribe类应该写着一个pub/sub机制的一些方法,并非仅仅是log机制使用。打开看看:

module ActiveSupport
  # 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 Active Record subscriber responsible for collecting
  # statistics about queries:
  #
  #   module ActiveRecord
  #     class StatsSubscriber < ActiveSupport::Subscriber
  #       def sql(event)
  #         Statsd.timing("sql.#{event.payload[:name]}", event.duration)
  #       end
  #     end
  #   end
  #
  # And it's finally registered as:
  #
  #   ActiveRecord::StatsSubscriber.attach_to :active_record
  #
  # Since we need to know all instance methods before attaching the log
  # subscriber, the line above should be called after your subscriber definition.
  #
  # After configured, whenever a "sql.active_record" notification is published,
  # it will properly dispatch the event (ActiveSupport::Notifications::Event) to
  # the +sql+ method.
  class Subscriber
    class << self

      # Attach the subscriber to a namespace.
      def attach_to(namespace, subscriber=new, notifier=ActiveSupport::Notifications)
        @namespace  = namespace
        @subscriber = subscriber
        @notifier   = notifier

        subscribers << subscriber

        # Add event subscribers for all existing methods on the class.
        subscriber.public_methods(false).each do |event|
          add_event_subscriber(event)
        end
      end

      # Adds event subscribers for all new methods added to the class.
      def method_added(event)
        # Only public methods are added as subscribers, and only if a notifier
        # has been set up. This means that subscribers will only be set up for
        # classes that call #attach_to.
        if public_method_defined?(event) && notifier
          add_event_subscriber(event)
        end
      end

      def subscribers
        @@subscribers ||= []
      end

      protected

      attr_reader :subscriber, :notifier, :namespace

      def add_event_subscriber(event)
        return if %w{ start finish }.include?(event.to_s)

        pattern = "#{event}.#{namespace}"

        # don't add multiple subscribers (eg. if methods are redefined)
        return if subscriber.patterns.include?(pattern)

        subscriber.patterns << pattern
        notifier.subscribe(pattern, subscriber)
      end
    end

    attr_reader :patterns # :nodoc:

    def initialize
      @queue_key = [self.class.name, object_id].join "-"
      @patterns  = []
      super
    end

    def start(name, id, payload)
      e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload)
      parent = event_stack.last
      parent << e if parent

      event_stack.push e
    end

    def finish(name, id, payload)
      finished  = Time.now
      event     = event_stack.pop
      event.end = finished
      event.payload.merge!(payload)

      method = name.split('.').first
      send(method, event)
    end

    private

      def event_stack
        SubscriberQueueRegistry.instance.get_queue(@queue_key)
      end
  end

  # This is a registry for all the event stacks kept for subscribers.
  #
  # See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
  # for further details.
  class SubscriberQueueRegistry # :nodoc:
    extend PerThreadRegistry

    def initialize
      @registry = {}
    end

    def get_queue(queue_key)
      @registry[queue_key] ||= []
    end
  end
end

也不是很难懂。 就是定义了公共的行为。

时间: 2024-10-11 05:34:02

rails 源码解析之log notification/subscribe的相关文章

[slf4j+log] 源码解析

slf4j: The Simple Logging Facade for java即 java简单的日志门面.统一定义了一系列的日志接口,使用户可以使用统一的接口来记录日志.logback,log4j等框架都实现了这些接口,启动时动态地决定真正的日志框架.本文以slf4j+logback的源码来讲解整个绑定和打印日志的流程. 手动阅读目录如下: 绑定日志框架 解析配置文件获取LoggerFactory:对于logback而言就是LoggerContext 获取Logger:在LoggerActi

Android EventBus源码解析, 带你深入理解EventBus

上一篇带大家初步了解了EventBus的使用方式,详见:Android EventBus实战 没听过你就out了,本篇博客将解析EventBus的源码,相信能够让大家深入理解该框架的实现,也能解决很多在使用中的疑问:为什么可以这么做?为什么这么做不好呢? 1.概述 一般使用EventBus的组件类,类似下面这种方式: [java] view plain copy public class SampleComponent extends Fragment { @Override public vo

Android EventBus3.0使用及源码解析

叨了个叨 最近因为换工作的一些琐事搞的我一个头两个大,也没怎么去学新东西,实在是有些愧疚.新项目用到了EventBus3.0,原来只是听说EventBus的鼎鼎大名,一直没仔细研究过.趁着周末有些时间,研究下代码,也算没有虚度光阴. EventBus GitHub : https://github.com/greenrobot/EventBus EventBus3.0简介 EventBus是greenrobot出品的一个用于Android中事件发布/订阅的库.以前传递对象可能通过接口.广播.文件

EventBus3.0源码解析

本文主要介绍EventBus3.0的源码 EventBus是一个Android事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递. EventBus使用简单,并将事件发布和订阅充分解耦,从而使代码更简洁. 本文主要从以下几个模块来介绍 1.EventBus使用 2.EventBus注册源码解析 3.EventBus事件分发解析 4.EventBus取消注册解析 一.EventBus使用 1.首先是注册 EventBus.getDefault().register(this)

RxJava2 源码解析(二)

转载请标明出处: http://blog.csdn.net/zxt0601/article/details/61637439 本文出自:[张旭童的博客](http://blog.csdn.net/zxt0601) 概述 承接上一篇RxJava2 源码解析(一), 本系列我们的目的: 知道源头(Observable)是如何将数据发送出去的. 知道终点(Observer)是如何接收到数据的. 何时将源头和终点关联起来的 知道线程调度是怎么实现的 知道操作符是怎么实现的 本篇计划讲解一下4,5. Rx

Android源码解析——Toast

简介 Toast是一种向用户快速提供少量信息的视图.当它显示时,它会浮在整个应用层的上面,并且不会获取到焦点.它的设计思想是能够向用户展示些信息,但又能尽量不显得唐突.本篇我们来研读一下Toast的源码,并探明它的显示及隐藏机制. 源码解析 Toast 我们从Toast的最简单调用开始,它的调用代码是: Toast.makeText(context,"Show toast",Toast.LENGTH_LONG).show(); 在上面的代码中,我们是先调用Toast的静态方法来创建一个

Redux系列x:源码解析

写在前面 redux的源码很简洁,除了applyMiddleware比较绕难以理解外,大部分还是 这里假设读者对redux有一定了解,就不科普redux的概念和API啥的啦,这部分建议直接看官方文档. 此外,源码解析的中文批注版已上传至github,可点击查看.本文相关示例代码,可点击查看. 源码解析概览 将redux下载下来,然后看下他的目录结构. npm install redux 这里我们需要关心的主要是src目录,源码解析需要关心的文件都在这里面了 index.js:redux主文件,主

浅谈RxJava源码解析(观察者),创建(create、from、just),变换(Map、flatMap)、线程调度

一.创建操作: 1.观察者模式:RxJava的世界里,我们有四种角色: Observable<T>(被观察者).Observer(观察者) Subscriber(订阅者).Subject Observable和Subject是两个"生产"实体,Observer和Subscriber是两个"消费"实体.Observable 和 Observer 通过 subscribe() 方法实现订阅关系,从而 Observable 可以在需要的时候发出事件来通知 Ob

Android EventBus源码解析 带你深入理解EventBus

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40920453,本文出自:[张鸿洋的博客] 上一篇带大家初步了解了EventBus的使用方式,详见:Android EventBus实战 没听过你就out了,本篇博客将解析EventBus的源码,相信能够让大家深入理解该框架的实现,也能解决很多在使用中的疑问:为什么可以这么做?为什么这么做不好呢? 1.概述 一般使用EventBus的组件类,类似下面这种方式: public cl