Permalink
Browse files

Extract a base class from ActiveSupport::LogSubscriber

Adds a ActiveSupport::Subscriber base class that LogSubscriber inherits
from. By inheriting from Subscriber, other kinds of subscribers can take
advantage of the event attachment system.
  • Loading branch information...
1 parent c78da9a commit 5f07048dfdcc86bf78d0a498c5f23fa4dec917f7 @dasch dasch committed Apr 15, 2013
@@ -1,5 +1,11 @@
## Rails 4.0.0 (unreleased) ##
+* An `ActiveSupport::Subscriber` class has been extracted from
+ `ActiveSupport::LogSubscriber`, allowing you to use the event attachment
+ API for other kinds of subscribers.
+
+ *Daniel Schierbeck*
+
* `Class#class_attribute` accepts an `instance_predicate` option which
defaults to `true`. If set to `false` the predicate method will not
be defined.
@@ -1,5 +1,6 @@
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/class/attribute'
+require 'active_support/subscriber'
module ActiveSupport
# ActiveSupport::LogSubscriber is an object set to consume
@@ -33,7 +34,7 @@ module ActiveSupport
# Log subscriber also has some helpers to deal with logging and automatically
# flushes all logs when the request finishes (via action_dispatch.callback
# notification) in a Rails environment.
- class LogSubscriber
+ class LogSubscriber < Subscriber
# Embed in a String to clear all previous ANSI sequences.
CLEAR = "\e[0m"
BOLD = "\e[1m"
@@ -60,18 +61,8 @@ def logger
attr_writer :logger
- def attach_to(namespace, log_subscriber=new, notifier=ActiveSupport::Notifications)
- log_subscribers << log_subscriber
-
- log_subscriber.public_methods(false).each do |event|
- next if %w{ start finish }.include?(event.to_s)
-
- notifier.subscribe("#{event}.#{namespace}", log_subscriber)
- end
- end
-
def log_subscribers
- @@log_subscribers ||= []
+ subscribers
end
# Flush all log_subscribers' logger.
@@ -80,39 +71,18 @@ def flush_all!
end
end
- def initialize
- @queue_key = [self.class.name, object_id].join "-"
- super
- end
-
def logger
LogSubscriber.logger
end
def start(name, id, payload)
- return unless logger
-
- e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload)
- parent = event_stack.last
- parent << e if parent
-
- event_stack.push e
+ super if logger
end
def finish(name, id, payload)
- return unless logger
-
- finished = Time.now
- event = event_stack.pop
- event.end = finished
- event.payload.merge!(payload)
-
- method = name.split('.').first
- begin
- send(method, event)
- rescue Exception => e
- logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
- end
+ super if logger
+ rescue Exception => e
+ logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
end
protected
@@ -135,11 +105,5 @@ def color(text, color, bold=false)
bold = bold ? BOLD : ""
"#{bold}#{color}#{text}#{CLEAR}"
end
-
- private
-
- def event_stack
- Thread.current[@queue_key] ||= []
- end
end
end
@@ -0,0 +1,76 @@
+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::Subscriber.attach_to :active_record
@fxn
fxn Apr 16, 2013 Member

This would be ActiveRecord::StatsSubscriber?

+ #
+ # Since we need to know all instance methods before attaching the log
+ # subscriber, the line above should be called after your
+ # <tt>ActiveRecord::Subscriber</tt> definition.
@fxn
fxn Apr 16, 2013 Member

I think this would be either ActiveRecord::StatsSubscriber or just "subscriber" in regular font.

+ #
+ # After configured, whenever a "sql.active_record" notification is published,
+ # it will properly dispatch the event (ActiveSupport::Notifications::Event) to
+ # the `sql` method.
@fxn
fxn Apr 16, 2013 Member

This is not RDoc markup.

+ class Subscriber
+ class << self
+
+ # Attach the subscriber to a namespace.
+ def attach_to(namespace, subscriber=new, notifier=ActiveSupport::Notifications)
+ subscribers << subscriber
+
+ subscriber.public_methods(false).each do |event|
+ next if %w{ start finish }.include?(event.to_s)
+
+ notifier.subscribe("#{event}.#{namespace}", subscriber)
+ end
+ end
+
+ def subscribers
+ @@subscribers ||= []
+ end
+ end
+
+ def initialize
+ @queue_key = [self.class.name, object_id].join "-"
+ 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
+ Thread.current[@queue_key] ||= []
+ end
+ end
+end

2 comments on commit 5f07048

@fxn
Member
fxn commented on 5f07048 Apr 16, 2013

Good extraction!

@dasch
Contributor
dasch commented on 5f07048 Apr 16, 2013

Thanks, and sorry for the typos :-)

Can I also peddle my equally good pull request: #10234 ? :-D

Please sign in to comment.