Skip to content

Commit

Permalink
Extract a base class from ActiveSupport::LogSubscriber
Browse files Browse the repository at this point in the history
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
dasch committed Apr 16, 2013
1 parent c78da9a commit 5f07048
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 43 deletions.
6 changes: 6 additions & 0 deletions activesupport/CHANGELOG.md
@@ -1,5 +1,11 @@
## Rails 4.0.0 (unreleased) ## ## 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 * `Class#class_attribute` accepts an `instance_predicate` option which
defaults to `true`. If set to `false` the predicate method will not defaults to `true`. If set to `false` the predicate method will not
be defined. be defined.
Expand Down
50 changes: 7 additions & 43 deletions activesupport/lib/active_support/log_subscriber.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/class/attribute'
require 'active_support/subscriber'


module ActiveSupport module ActiveSupport
# ActiveSupport::LogSubscriber is an object set to consume # ActiveSupport::LogSubscriber is an object set to consume
Expand Down Expand Up @@ -33,7 +34,7 @@ module ActiveSupport
# Log subscriber also has some helpers to deal with logging and automatically # Log subscriber also has some helpers to deal with logging and automatically
# flushes all logs when the request finishes (via action_dispatch.callback # flushes all logs when the request finishes (via action_dispatch.callback
# notification) in a Rails environment. # notification) in a Rails environment.
class LogSubscriber class LogSubscriber < Subscriber
# Embed in a String to clear all previous ANSI sequences. # Embed in a String to clear all previous ANSI sequences.
CLEAR = "\e[0m" CLEAR = "\e[0m"
BOLD = "\e[1m" BOLD = "\e[1m"
Expand All @@ -60,18 +61,8 @@ def logger


attr_writer :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 def log_subscribers
@@log_subscribers ||= [] subscribers
end end


# Flush all log_subscribers' logger. # Flush all log_subscribers' logger.
Expand All @@ -80,39 +71,18 @@ def flush_all!
end end
end end


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

def logger def logger
LogSubscriber.logger LogSubscriber.logger
end end


def start(name, id, payload) def start(name, id, payload)
return unless logger super if logger

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

event_stack.push e
end end


def finish(name, id, payload) def finish(name, id, payload)
return unless logger super if logger

rescue Exception => e
finished = Time.now logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
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
end end


protected protected
Expand All @@ -135,11 +105,5 @@ def color(text, color, bold=false)
bold = bold ? BOLD : "" bold = bold ? BOLD : ""
"#{bold}#{color}#{text}#{CLEAR}" "#{bold}#{color}#{text}#{CLEAR}"
end end

private

def event_stack
Thread.current[@queue_key] ||= []
end
end end
end end
76 changes: 76 additions & 0 deletions activesupport/lib/active_support/subscriber.rb
@@ -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

This comment has been minimized.

Copy link
@fxn

fxn Apr 16, 2013

Member

This would be ActiveRecord::StatsSubscriber?

This comment has been minimized.

Copy link
@rafaelfranca

rafaelfranca Apr 16, 2013

Member

Fixed

#
# 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.

This comment has been minimized.

Copy link
@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.

This comment has been minimized.

Copy link
@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
Copy link
Member

@fxn fxn commented on 5f07048 Apr 16, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good extraction!

@dasch
Copy link
Contributor Author

@dasch dasch commented on 5f07048 Apr 16, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, and sorry for the typos :-)

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

Please sign in to comment.