您的位置:首页 > 大数据 > 人工智能

rails 源码解析之log notification/subscribe

2014-12-14 15:40 281 查看
我调试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 = !@odd
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


也不是很难懂。 就是定义了公共的行为。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: