activesupport_notifications_backport 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +134 -0
- data/activesupport_notifications_backport.gemspec +62 -0
- data/lib/active_support/log_subscriber.rb +122 -0
- data/lib/active_support/log_subscriber/test_helper.rb +102 -0
- data/lib/active_support/notifications.rb +154 -0
- data/lib/active_support/notifications/fanout.rb +60 -0
- data/lib/active_support/notifications/instrumenter.rb +51 -0
- data/lib/activesupport_notifications_backport.rb +12 -0
- data/test/abstract_unit.rb +5 -0
- data/test/log_subscriber_test.rb +123 -0
- data/test/notifications_test.rb +242 -0
- metadata +102 -0
data/Rakefile
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
#############################################################################
|
6
|
+
#
|
7
|
+
# Helper functions
|
8
|
+
#
|
9
|
+
#############################################################################
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
13
|
+
end
|
14
|
+
|
15
|
+
def version
|
16
|
+
line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
|
17
|
+
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def date
|
21
|
+
Date.today.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def rubyforge_project
|
25
|
+
name
|
26
|
+
end
|
27
|
+
|
28
|
+
def gemspec_file
|
29
|
+
"#{name}.gemspec"
|
30
|
+
end
|
31
|
+
|
32
|
+
def gem_file
|
33
|
+
"#{name}-#{version}.gem"
|
34
|
+
end
|
35
|
+
|
36
|
+
def replace_header(head, header_name)
|
37
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
38
|
+
end
|
39
|
+
|
40
|
+
#############################################################################
|
41
|
+
#
|
42
|
+
# Standard tasks
|
43
|
+
#
|
44
|
+
#############################################################################
|
45
|
+
|
46
|
+
task :default => :test
|
47
|
+
|
48
|
+
require 'rake/testtask'
|
49
|
+
Rake::TestTask.new(:test) do |test|
|
50
|
+
test.libs << 'lib' << 'test'
|
51
|
+
test.pattern = 'test/**/*_test.rb'
|
52
|
+
test.verbose = true
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Open an irb session preloaded with this library"
|
56
|
+
task :console do
|
57
|
+
sh "irb -rubygems -r ./lib/#{name}.rb"
|
58
|
+
end
|
59
|
+
|
60
|
+
#############################################################################
|
61
|
+
#
|
62
|
+
# Custom tasks (add your own tasks here)
|
63
|
+
#
|
64
|
+
#############################################################################
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
#############################################################################
|
69
|
+
#
|
70
|
+
# Packaging tasks
|
71
|
+
#
|
72
|
+
#############################################################################
|
73
|
+
|
74
|
+
desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
|
75
|
+
task :release => :build do
|
76
|
+
unless `git branch` =~ /^\* master$/
|
77
|
+
puts "You must be on the master branch to release!"
|
78
|
+
exit!
|
79
|
+
end
|
80
|
+
sh "git commit --allow-empty -a -m 'Release #{version}'"
|
81
|
+
sh "git tag v#{version}"
|
82
|
+
sh "git push origin master"
|
83
|
+
sh "git push origin v#{version}"
|
84
|
+
sh "gem push pkg/#{gem_file}"
|
85
|
+
end
|
86
|
+
|
87
|
+
desc "Build #{gem_file} into the pkg directory"
|
88
|
+
task :build => :gemspec do
|
89
|
+
sh "mkdir -p pkg"
|
90
|
+
sh "gem build #{gemspec_file}"
|
91
|
+
sh "mv #{gem_file} pkg"
|
92
|
+
end
|
93
|
+
|
94
|
+
desc "Generate #{gemspec_file}"
|
95
|
+
task :gemspec => :validate do
|
96
|
+
# read spec file and split out manifest section
|
97
|
+
spec = File.read(gemspec_file)
|
98
|
+
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
99
|
+
|
100
|
+
# replace name version and date
|
101
|
+
replace_header(head, :name)
|
102
|
+
replace_header(head, :version)
|
103
|
+
replace_header(head, :date)
|
104
|
+
#comment this out if your rubyforge_project has a different name
|
105
|
+
replace_header(head, :rubyforge_project)
|
106
|
+
|
107
|
+
# determine file list from git ls-files
|
108
|
+
files = `git ls-files`.
|
109
|
+
split("\n").
|
110
|
+
sort.
|
111
|
+
reject { |file| file =~ /^\./ }.
|
112
|
+
reject { |file| file =~ /^(rdoc|pkg)/ }.
|
113
|
+
map { |file| " #{file}" }.
|
114
|
+
join("\n")
|
115
|
+
|
116
|
+
# piece file back together and write
|
117
|
+
manifest = " s.files = %w[\n#{files}\n ]\n"
|
118
|
+
spec = [head, manifest, tail].join(" # = MANIFEST =\n")
|
119
|
+
File.open(gemspec_file, 'w') { |io| io.write(spec) }
|
120
|
+
puts "Updated #{gemspec_file}"
|
121
|
+
end
|
122
|
+
|
123
|
+
desc "Validate #{gemspec_file}"
|
124
|
+
task :validate do
|
125
|
+
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
|
126
|
+
unless libfiles.empty?
|
127
|
+
puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
|
128
|
+
#exit!
|
129
|
+
end
|
130
|
+
unless Dir['VERSION*'].empty?
|
131
|
+
puts "A `VERSION` file at root level violates Gem best practices."
|
132
|
+
exit!
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
## This is the rakegem gemspec template. Make sure you read and understand
|
2
|
+
## all of the comments. Some sections require modification, and others can
|
3
|
+
## be deleted if you don't need them. Once you understand the contents of
|
4
|
+
## this file, feel free to delete any comments that begin with two hash marks.
|
5
|
+
## You can find comprehensive Gem::Specification documentation, at
|
6
|
+
## http://docs.rubygems.org/read/chapter/20
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
9
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.3.5") if s.respond_to? :required_rubygems_version=
|
10
|
+
|
11
|
+
## Leave these as is they will be modified for you by the rake gemspec task.
|
12
|
+
## If your rubyforge_project name is different, then edit it and comment out
|
13
|
+
## the sub! line in the Rakefile
|
14
|
+
s.name = 'activesupport_notifications_backport'
|
15
|
+
s.version = '0.0.1'
|
16
|
+
s.date = '2012-03-20'
|
17
|
+
s.rubyforge_project = 'activesupport_notifications_backport'
|
18
|
+
|
19
|
+
## Make sure your summary is short. The description may be as long
|
20
|
+
## as you like.
|
21
|
+
s.summary = "ActiveSupport::Notifications backported for Rails 2.3"
|
22
|
+
s.description = "ActiveSupport::Notifications backported for Rails 2.3"
|
23
|
+
|
24
|
+
## List the primary authors. If there are a bunch of authors, it's probably
|
25
|
+
## better to set the email to an email list or something. If you don't have
|
26
|
+
## a custom homepage, consider using your GitHub URL or the like.
|
27
|
+
s.authors = ["Rick Olson"]
|
28
|
+
s.email = 'technoweenie@gmail.com'
|
29
|
+
s.homepage = 'http://github.com/technoweenie/activesupport_notifications_backport'
|
30
|
+
|
31
|
+
## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
|
32
|
+
## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
|
33
|
+
s.require_paths = %w[lib]
|
34
|
+
|
35
|
+
s.add_dependency 'activesupport', '~> 2.3.14'
|
36
|
+
s.add_development_dependency 'rake'
|
37
|
+
s.add_development_dependency 'mocha'
|
38
|
+
s.add_development_dependency 'test-unit'
|
39
|
+
|
40
|
+
## Leave this section as-is. It will be automatically generated from the
|
41
|
+
## contents of your Git repository via the gemspec task. DO NOT REMOVE
|
42
|
+
## THE MANIFEST COMMENTS, they are used as delimiters by the task.
|
43
|
+
# = MANIFEST =
|
44
|
+
s.files = %w[
|
45
|
+
Rakefile
|
46
|
+
activesupport_notifications_backport.gemspec
|
47
|
+
lib/active_support/log_subscriber.rb
|
48
|
+
lib/active_support/log_subscriber/test_helper.rb
|
49
|
+
lib/active_support/notifications.rb
|
50
|
+
lib/active_support/notifications/fanout.rb
|
51
|
+
lib/active_support/notifications/instrumenter.rb
|
52
|
+
lib/activesupport_notifications_backport.rb
|
53
|
+
test/abstract_unit.rb
|
54
|
+
test/log_subscriber_test.rb
|
55
|
+
test/notifications_test.rb
|
56
|
+
]
|
57
|
+
# = MANIFEST =
|
58
|
+
|
59
|
+
## Test files will be grabbed from the file list. Make sure the path glob
|
60
|
+
## matches what you actually use.
|
61
|
+
s.test_files = s.files.select { |path| path =~ %r{^test/*/.+\.rb} }
|
62
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
2
|
+
require 'active_support/core_ext/class/attribute'
|
3
|
+
|
4
|
+
module ActiveSupport
|
5
|
+
# ActiveSupport::LogSubscriber is an object set to consume ActiveSupport::Notifications
|
6
|
+
# with the sole purpose of logging them. The log subscriber dispatches notifications to
|
7
|
+
# a registered object based on its given namespace.
|
8
|
+
#
|
9
|
+
# An example would be Active Record log subscriber responsible for logging queries:
|
10
|
+
#
|
11
|
+
# module ActiveRecord
|
12
|
+
# class LogSubscriber < ActiveSupport::LogSubscriber
|
13
|
+
# def sql(event)
|
14
|
+
# "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}"
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# And it's finally registered as:
|
20
|
+
#
|
21
|
+
# ActiveRecord::LogSubscriber.attach_to :active_record
|
22
|
+
#
|
23
|
+
# Since we need to know all instance methods before attaching the log subscriber,
|
24
|
+
# the line above should be called after your <tt>ActiveRecord::LogSubscriber</tt> definition.
|
25
|
+
#
|
26
|
+
# After configured, whenever a "sql.active_record" notification is published,
|
27
|
+
# it will properly dispatch the event (ActiveSupport::Notifications::Event) to
|
28
|
+
# the sql method.
|
29
|
+
#
|
30
|
+
# Log subscriber also has some helpers to deal with logging and automatically flushes
|
31
|
+
# all logs when the request finishes (via action_dispatch.callback notification) in
|
32
|
+
# a Rails environment.
|
33
|
+
class LogSubscriber
|
34
|
+
# Embed in a String to clear all previous ANSI sequences.
|
35
|
+
CLEAR = "\e[0m"
|
36
|
+
BOLD = "\e[1m"
|
37
|
+
|
38
|
+
# Colors
|
39
|
+
BLACK = "\e[30m"
|
40
|
+
RED = "\e[31m"
|
41
|
+
GREEN = "\e[32m"
|
42
|
+
YELLOW = "\e[33m"
|
43
|
+
BLUE = "\e[34m"
|
44
|
+
MAGENTA = "\e[35m"
|
45
|
+
CYAN = "\e[36m"
|
46
|
+
WHITE = "\e[37m"
|
47
|
+
|
48
|
+
mattr_accessor :colorize_logging
|
49
|
+
self.colorize_logging = true
|
50
|
+
|
51
|
+
class_attribute :logger
|
52
|
+
|
53
|
+
class << self
|
54
|
+
remove_method :logger
|
55
|
+
def logger
|
56
|
+
@logger ||= Rails.logger if defined?(Rails)
|
57
|
+
end
|
58
|
+
|
59
|
+
def attach_to(namespace, log_subscriber=new, notifier=ActiveSupport::Notifications)
|
60
|
+
log_subscribers << log_subscriber
|
61
|
+
@@flushable_loggers = nil
|
62
|
+
|
63
|
+
log_subscriber.public_methods(false).each do |event|
|
64
|
+
next if 'call' == event.to_s
|
65
|
+
|
66
|
+
notifier.subscribe("#{event}.#{namespace}", log_subscriber)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def log_subscribers
|
71
|
+
@@log_subscribers ||= []
|
72
|
+
end
|
73
|
+
|
74
|
+
def flushable_loggers
|
75
|
+
@@flushable_loggers ||= begin
|
76
|
+
loggers = log_subscribers.map(&:logger)
|
77
|
+
loggers.uniq!
|
78
|
+
loggers.select! { |l| l.respond_to?(:flush) }
|
79
|
+
loggers
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Flush all log_subscribers' logger.
|
84
|
+
def flush_all!
|
85
|
+
flushable_loggers.each { |log| log.flush }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def call(message, *args)
|
90
|
+
return unless logger
|
91
|
+
|
92
|
+
method = message.split('.').first
|
93
|
+
begin
|
94
|
+
send(method, ActiveSupport::Notifications::Event.new(message, *args))
|
95
|
+
rescue Exception => e
|
96
|
+
logger.error "Could not log #{message.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
|
102
|
+
%w(info debug warn error fatal unknown).each do |level|
|
103
|
+
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
104
|
+
def #{level}(progname = nil, &block)
|
105
|
+
logger.#{level}(progname, &block) if logger
|
106
|
+
end
|
107
|
+
METHOD
|
108
|
+
end
|
109
|
+
|
110
|
+
# Set color by using a string or one of the defined constants. If a third
|
111
|
+
# option is set to true, it also adds bold to the string. This is based
|
112
|
+
# on the Highline implementation and will automatically append CLEAR to the
|
113
|
+
# end of the returned String.
|
114
|
+
#
|
115
|
+
def color(text, color, bold=false)
|
116
|
+
return text unless colorize_logging
|
117
|
+
color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol)
|
118
|
+
bold = bold ? BOLD : ""
|
119
|
+
"#{bold}#{color}#{text}#{CLEAR}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require File.expand_path('../../log_subscriber', __FILE__)
|
2
|
+
require 'active_support/buffered_logger'
|
3
|
+
require File.expand_path('../../notifications', __FILE__)
|
4
|
+
|
5
|
+
module ActiveSupport
|
6
|
+
Logger = BufferedLogger
|
7
|
+
class LogSubscriber
|
8
|
+
# Provides some helpers to deal with testing log subscribers by setting up
|
9
|
+
# notifications. Take for instance Active Record subscriber tests:
|
10
|
+
#
|
11
|
+
# class SyncLogSubscriberTest < ActiveSupport::TestCase
|
12
|
+
# include ActiveSupport::LogSubscriber::TestHelper
|
13
|
+
#
|
14
|
+
# def setup
|
15
|
+
# ActiveRecord::LogSubscriber.attach_to(:active_record)
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# def test_basic_query_logging
|
19
|
+
# Developer.all
|
20
|
+
# wait
|
21
|
+
# assert_equal 1, @logger.logged(:debug).size
|
22
|
+
# assert_match(/Developer Load/, @logger.logged(:debug).last)
|
23
|
+
# assert_match(/SELECT \* FROM "developers"/, @logger.logged(:debug).last)
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# All you need to do is to ensure that your log subscriber is added to Rails::Subscriber,
|
28
|
+
# as in the second line of the code above. The test helpers are responsible for setting
|
29
|
+
# up the queue, subscriptions and turning colors in logs off.
|
30
|
+
#
|
31
|
+
# The messages are available in the @logger instance, which is a logger with limited
|
32
|
+
# powers (it actually does not send anything to your output), and you can collect them
|
33
|
+
# doing @logger.logged(level), where level is the level used in logging, like info,
|
34
|
+
# debug, warn and so on.
|
35
|
+
#
|
36
|
+
module TestHelper
|
37
|
+
def setup
|
38
|
+
@logger = MockLogger.new
|
39
|
+
@notifier = ActiveSupport::Notifications::Fanout.new
|
40
|
+
|
41
|
+
ActiveSupport::LogSubscriber.colorize_logging = false
|
42
|
+
|
43
|
+
@old_notifier = ActiveSupport::Notifications.notifier
|
44
|
+
set_logger(@logger)
|
45
|
+
ActiveSupport::Notifications.notifier = @notifier
|
46
|
+
end
|
47
|
+
|
48
|
+
def teardown
|
49
|
+
set_logger(nil)
|
50
|
+
ActiveSupport::Notifications.notifier = @old_notifier
|
51
|
+
end
|
52
|
+
|
53
|
+
class MockLogger
|
54
|
+
include ActiveSupport::Logger::Severity
|
55
|
+
|
56
|
+
attr_reader :flush_count
|
57
|
+
attr_accessor :level
|
58
|
+
|
59
|
+
def initialize(level = DEBUG)
|
60
|
+
@flush_count = 0
|
61
|
+
@level = level
|
62
|
+
@logged = Hash.new { |h,k| h[k] = [] }
|
63
|
+
end
|
64
|
+
|
65
|
+
def method_missing(level, message)
|
66
|
+
@logged[level] << message
|
67
|
+
end
|
68
|
+
|
69
|
+
def logged(level)
|
70
|
+
@logged[level].compact.map { |l| l.to_s.strip }
|
71
|
+
end
|
72
|
+
|
73
|
+
def flush
|
74
|
+
@flush_count += 1
|
75
|
+
end
|
76
|
+
|
77
|
+
ActiveSupport::Logger::Severity.constants.each do |severity|
|
78
|
+
class_eval <<-EOT, __FILE__, __LINE__ + 1
|
79
|
+
def #{severity.downcase}?
|
80
|
+
#{severity} >= @level
|
81
|
+
end
|
82
|
+
EOT
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Wait notifications to be published.
|
87
|
+
def wait
|
88
|
+
@notifier.wait
|
89
|
+
end
|
90
|
+
|
91
|
+
# Overwrite if you use another logger in your log subscriber:
|
92
|
+
#
|
93
|
+
# def logger
|
94
|
+
# ActiveRecord::Base.logger = @logger
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
def set_logger(logger)
|
98
|
+
ActiveSupport::LogSubscriber.logger = logger
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module ActiveSupport
|
2
|
+
# = Notifications
|
3
|
+
#
|
4
|
+
# +ActiveSupport::Notifications+ provides an instrumentation API for Ruby.
|
5
|
+
#
|
6
|
+
# == Instrumenters
|
7
|
+
#
|
8
|
+
# To instrument an event you just need to do:
|
9
|
+
#
|
10
|
+
# ActiveSupport::Notifications.instrument("render", :extra => :information) do
|
11
|
+
# render :text => "Foo"
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# That executes the block first and notifies all subscribers once done.
|
15
|
+
#
|
16
|
+
# In the example above "render" is the name of the event, and the rest is called
|
17
|
+
# the _payload_. The payload is a mechanism that allows instrumenters to pass
|
18
|
+
# extra information to subscribers. Payloads consist of a hash whose contents
|
19
|
+
# are arbitrary and generally depend on the event.
|
20
|
+
#
|
21
|
+
# == Subscribers
|
22
|
+
#
|
23
|
+
# You can consume those events and the information they provide by registering
|
24
|
+
# a subscriber. For instance, let's store all "render" events in an array:
|
25
|
+
#
|
26
|
+
# events = []
|
27
|
+
#
|
28
|
+
# ActiveSupport::Notifications.subscribe("render") do |*args|
|
29
|
+
# events << ActiveSupport::Notifications::Event.new(*args)
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# That code returns right away, you are just subscribing to "render" events.
|
33
|
+
# The block will be called asynchronously whenever someone instruments "render":
|
34
|
+
#
|
35
|
+
# ActiveSupport::Notifications.instrument("render", :extra => :information) do
|
36
|
+
# render :text => "Foo"
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# event = events.first
|
40
|
+
# event.name # => "render"
|
41
|
+
# event.duration # => 10 (in milliseconds)
|
42
|
+
# event.payload # => { :extra => :information }
|
43
|
+
#
|
44
|
+
# The block in the +subscribe+ call gets the name of the event, start
|
45
|
+
# timestamp, end timestamp, a string with a unique identifier for that event
|
46
|
+
# (something like "535801666f04d0298cd6"), and a hash with the payload, in
|
47
|
+
# that order.
|
48
|
+
#
|
49
|
+
# If an exception happens during that particular instrumentation the payload will
|
50
|
+
# have a key +:exception+ with an array of two elements as value: a string with
|
51
|
+
# the name of the exception class, and the exception message.
|
52
|
+
#
|
53
|
+
# As the previous example depicts, the class +ActiveSupport::Notifications::Event+
|
54
|
+
# is able to take the arguments as they come and provide an object-oriented
|
55
|
+
# interface to that data.
|
56
|
+
#
|
57
|
+
# You can also subscribe to all events whose name matches a certain regexp:
|
58
|
+
#
|
59
|
+
# ActiveSupport::Notifications.subscribe(/render/) do |*args|
|
60
|
+
# ...
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# and even pass no argument to +subscribe+, in which case you are subscribing
|
64
|
+
# to all events.
|
65
|
+
#
|
66
|
+
# == Temporary Subscriptions
|
67
|
+
#
|
68
|
+
# Sometimes you do not want to subscribe to an event for the entire life of
|
69
|
+
# the application. There are two ways to unsubscribe.
|
70
|
+
#
|
71
|
+
# WARNING: The instrumentation framework is designed for long-running subscribers,
|
72
|
+
# use this feature sparingly because it wipes some internal caches and that has
|
73
|
+
# a negative impact on performance.
|
74
|
+
#
|
75
|
+
# === Subscribe While a Block Runs
|
76
|
+
#
|
77
|
+
# You can subscribe to some event temporarily while some block runs. For
|
78
|
+
# example, in
|
79
|
+
#
|
80
|
+
# callback = lambda {|*args| ... }
|
81
|
+
# ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do
|
82
|
+
# ...
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# the callback will be called for all "sql.active_record" events instrumented
|
86
|
+
# during the execution of the block. The callback is unsubscribed automatically
|
87
|
+
# after that.
|
88
|
+
#
|
89
|
+
# === Manual Unsubscription
|
90
|
+
#
|
91
|
+
# The +subscribe+ method returns a subscriber object:
|
92
|
+
#
|
93
|
+
# subscriber = ActiveSupport::Notifications.subscribe("render") do |*args|
|
94
|
+
# ...
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# To prevent that block from being called anymore, just unsubscribe passing
|
98
|
+
# that reference:
|
99
|
+
#
|
100
|
+
# ActiveSupport::Notifications.unsubscribe(subscriber)
|
101
|
+
#
|
102
|
+
# == Default Queue
|
103
|
+
#
|
104
|
+
# Notifications ships with a queue implementation that consumes and publish events
|
105
|
+
# to log subscribers in a thread. You can use any queue implementation you want.
|
106
|
+
#
|
107
|
+
module Notifications
|
108
|
+
autoload :Instrumenter, File.expand_path('../notifications/instrumenter', __FILE__)
|
109
|
+
autoload :Event, File.expand_path('../notifications/instrumenter', __FILE__)
|
110
|
+
autoload :Fanout, File.expand_path('../notifications/fanout', __FILE__)
|
111
|
+
|
112
|
+
@instrumenters = Hash.new { |h,k| h[k] = notifier.listening?(k) }
|
113
|
+
|
114
|
+
class << self
|
115
|
+
attr_accessor :notifier
|
116
|
+
|
117
|
+
def publish(name, *args)
|
118
|
+
notifier.publish(name, *args)
|
119
|
+
end
|
120
|
+
|
121
|
+
def instrument(name, payload = {})
|
122
|
+
if @instrumenters[name]
|
123
|
+
instrumenter.instrument(name, payload) { yield payload if block_given? }
|
124
|
+
else
|
125
|
+
yield payload if block_given?
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def subscribe(*args, &block)
|
130
|
+
notifier.subscribe(*args, &block).tap do
|
131
|
+
@instrumenters.clear
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def subscribed(callback, *args, &block)
|
136
|
+
subscriber = subscribe(*args, &callback)
|
137
|
+
yield
|
138
|
+
ensure
|
139
|
+
unsubscribe(subscriber)
|
140
|
+
end
|
141
|
+
|
142
|
+
def unsubscribe(args)
|
143
|
+
notifier.unsubscribe(args)
|
144
|
+
@instrumenters.clear
|
145
|
+
end
|
146
|
+
|
147
|
+
def instrumenter
|
148
|
+
Thread.current[:"instrumentation_#{notifier.object_id}"] ||= Instrumenter.new(notifier)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
self.notifier = Fanout.new
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module ActiveSupport
|
2
|
+
module Notifications
|
3
|
+
# This is a default queue implementation that ships with Notifications.
|
4
|
+
# It just pushes events to all registered log subscribers.
|
5
|
+
class Fanout
|
6
|
+
def initialize
|
7
|
+
@subscribers = []
|
8
|
+
@listeners_for = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def subscribe(pattern = nil, block = Proc.new)
|
12
|
+
subscriber = Subscriber.new(pattern, block)
|
13
|
+
@subscribers << subscriber
|
14
|
+
@listeners_for.clear
|
15
|
+
subscriber
|
16
|
+
end
|
17
|
+
|
18
|
+
def unsubscribe(subscriber)
|
19
|
+
@subscribers.reject! { |s| s.matches?(subscriber) }
|
20
|
+
@listeners_for.clear
|
21
|
+
end
|
22
|
+
|
23
|
+
def publish(name, *args)
|
24
|
+
listeners_for(name).each { |s| s.publish(name, *args) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def listeners_for(name)
|
28
|
+
@listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def listening?(name)
|
32
|
+
listeners_for(name).any?
|
33
|
+
end
|
34
|
+
|
35
|
+
# This is a sync queue, so there is no waiting.
|
36
|
+
def wait
|
37
|
+
end
|
38
|
+
|
39
|
+
class Subscriber #:nodoc:
|
40
|
+
def initialize(pattern, delegate)
|
41
|
+
@pattern = pattern
|
42
|
+
@delegate = delegate
|
43
|
+
end
|
44
|
+
|
45
|
+
def publish(message, *args)
|
46
|
+
@delegate.call(message, *args)
|
47
|
+
end
|
48
|
+
|
49
|
+
def subscribed_to?(name)
|
50
|
+
!@pattern || @pattern === name.to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
def matches?(subscriber_or_name)
|
54
|
+
self === subscriber_or_name ||
|
55
|
+
@pattern && @pattern === subscriber_or_name
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module ActiveSupport
|
2
|
+
module Notifications
|
3
|
+
class Instrumenter
|
4
|
+
attr_reader :id
|
5
|
+
|
6
|
+
def initialize(notifier)
|
7
|
+
@id = unique_id
|
8
|
+
@notifier = notifier
|
9
|
+
end
|
10
|
+
|
11
|
+
# Instrument the given block by measuring the time taken to execute it
|
12
|
+
# and publish it. Notice that events get sent even if an error occurs
|
13
|
+
# in the passed-in block
|
14
|
+
def instrument(name, payload={})
|
15
|
+
started = Time.now
|
16
|
+
|
17
|
+
begin
|
18
|
+
yield
|
19
|
+
rescue Exception => e
|
20
|
+
payload[:exception] = [e.class.name, e.message]
|
21
|
+
raise e
|
22
|
+
ensure
|
23
|
+
@notifier.publish(name, started, Time.now, @id, payload)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def unique_id
|
29
|
+
SecureRandom.hex(10)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Event
|
34
|
+
attr_reader :name, :time, :end, :transaction_id, :payload, :duration
|
35
|
+
|
36
|
+
def initialize(name, start, ending, transaction_id, payload)
|
37
|
+
@name = name
|
38
|
+
@payload = payload.dup
|
39
|
+
@time = start
|
40
|
+
@transaction_id = transaction_id
|
41
|
+
@end = ending
|
42
|
+
@duration = 1000.0 * (@end - @time)
|
43
|
+
end
|
44
|
+
|
45
|
+
def parent_of?(event)
|
46
|
+
start = (time - event.time) * 1000
|
47
|
+
start <= 0 && (start + duration >= event.duration)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'active_support/version'
|
2
|
+
if ActiveSupport::VERSION::MAJOR > 2
|
3
|
+
raise "Can't use this with Rails 3+, silly!"
|
4
|
+
end
|
5
|
+
|
6
|
+
module ActiveSupportNotificationsBackport
|
7
|
+
VERSION = "0.0.1"
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'active_support/secure_random'
|
11
|
+
require File.expand_path('../active_support/notifications', __FILE__)
|
12
|
+
require File.expand_path('../active_support/log_subscriber', __FILE__)
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require File.expand_path('../abstract_unit', __FILE__)
|
2
|
+
require File.expand_path('../../lib/active_support/log_subscriber/test_helper', __FILE__)
|
3
|
+
|
4
|
+
class MyLogSubscriber < ActiveSupport::LogSubscriber
|
5
|
+
attr_reader :event
|
6
|
+
|
7
|
+
def some_event(event)
|
8
|
+
@event = event
|
9
|
+
info event.name
|
10
|
+
end
|
11
|
+
|
12
|
+
def foo(event)
|
13
|
+
debug "debug"
|
14
|
+
info "info"
|
15
|
+
warn "warn"
|
16
|
+
end
|
17
|
+
|
18
|
+
def bar(event)
|
19
|
+
info "#{color("cool", :red)}, #{color("isn't it?", :blue, true)}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def puke(event)
|
23
|
+
raise "puke"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class SyncLogSubscriberTest < ActiveSupport::TestCase
|
28
|
+
include ActiveSupport::LogSubscriber::TestHelper
|
29
|
+
|
30
|
+
def setup
|
31
|
+
super
|
32
|
+
@log_subscriber = MyLogSubscriber.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def teardown
|
36
|
+
super
|
37
|
+
ActiveSupport::LogSubscriber.log_subscribers.clear
|
38
|
+
end
|
39
|
+
|
40
|
+
def instrument(*args, &block)
|
41
|
+
ActiveSupport::Notifications.instrument(*args, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_proxies_method_to_rails_logger
|
45
|
+
@log_subscriber.foo(nil)
|
46
|
+
assert_equal %w(debug), @logger.logged(:debug)
|
47
|
+
assert_equal %w(info), @logger.logged(:info)
|
48
|
+
assert_equal %w(warn), @logger.logged(:warn)
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_set_color_for_messages
|
52
|
+
ActiveSupport::LogSubscriber.colorize_logging = true
|
53
|
+
@log_subscriber.bar(nil)
|
54
|
+
assert_equal "\e[31mcool\e[0m, \e[1m\e[34misn't it?\e[0m", @logger.logged(:info).last
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_does_not_set_color_if_colorize_logging_is_set_to_false
|
58
|
+
@log_subscriber.bar(nil)
|
59
|
+
assert_equal "cool, isn't it?", @logger.logged(:info).last
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_event_is_sent_to_the_registered_class
|
63
|
+
ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
|
64
|
+
instrument "some_event.my_log_subscriber"
|
65
|
+
wait
|
66
|
+
assert_equal %w(some_event.my_log_subscriber), @logger.logged(:info)
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_event_is_an_active_support_notifications_event
|
70
|
+
ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
|
71
|
+
instrument "some_event.my_log_subscriber"
|
72
|
+
wait
|
73
|
+
assert_kind_of ActiveSupport::Notifications::Event, @log_subscriber.event
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_does_not_send_the_event_if_it_doesnt_match_the_class
|
77
|
+
ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
|
78
|
+
instrument "unknown_event.my_log_subscriber"
|
79
|
+
wait
|
80
|
+
# If we get here, it means that NoMethodError was not raised.
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_does_not_send_the_event_if_logger_is_nil
|
84
|
+
ActiveSupport::LogSubscriber.logger = nil
|
85
|
+
@log_subscriber.expects(:some_event).never
|
86
|
+
ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
|
87
|
+
instrument "some_event.my_log_subscriber"
|
88
|
+
wait
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_does_not_fail_with_non_namespaced_events
|
92
|
+
ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
|
93
|
+
instrument "whatever"
|
94
|
+
wait
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_flushes_loggers
|
98
|
+
ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
|
99
|
+
ActiveSupport::LogSubscriber.flush_all!
|
100
|
+
assert_equal 1, @logger.flush_count
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_flushes_the_same_logger_just_once
|
104
|
+
ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
|
105
|
+
ActiveSupport::LogSubscriber.attach_to :another, @log_subscriber
|
106
|
+
ActiveSupport::LogSubscriber.flush_all!
|
107
|
+
wait
|
108
|
+
assert_equal 1, @logger.flush_count
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_logging_does_not_die_on_failures
|
112
|
+
ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
|
113
|
+
instrument "puke.my_log_subscriber"
|
114
|
+
instrument "some_event.my_log_subscriber"
|
115
|
+
wait
|
116
|
+
|
117
|
+
assert_equal 1, @logger.logged(:info).size
|
118
|
+
assert_equal 'some_event.my_log_subscriber', @logger.logged(:info).last
|
119
|
+
|
120
|
+
assert_equal 1, @logger.logged(:error).size
|
121
|
+
assert_match 'Could not log "puke.my_log_subscriber" event. RuntimeError: puke', @logger.logged(:error).last
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
require File.expand_path('../abstract_unit', __FILE__)
|
2
|
+
require 'active_support/core_ext/module/delegation'
|
3
|
+
|
4
|
+
module Notifications
|
5
|
+
class TestCase < ActiveSupport::TestCase
|
6
|
+
def setup
|
7
|
+
@old_notifier = ActiveSupport::Notifications.notifier
|
8
|
+
@notifier = ActiveSupport::Notifications::Fanout.new
|
9
|
+
ActiveSupport::Notifications.notifier = @notifier
|
10
|
+
@events = []
|
11
|
+
@named_events = []
|
12
|
+
@subscription = @notifier.subscribe { |*args| @events << event(*args) }
|
13
|
+
@named_subscription = @notifier.subscribe("named.subscription") { |*args| @named_events << event(*args) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def teardown
|
17
|
+
ActiveSupport::Notifications.notifier = @old_notifier
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def event(*args)
|
23
|
+
ActiveSupport::Notifications::Event.new(*args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class SubscribedTest < TestCase
|
28
|
+
def test_subscribed
|
29
|
+
name = "foo"
|
30
|
+
name2 = name * 2
|
31
|
+
expected = [name, name]
|
32
|
+
|
33
|
+
events = []
|
34
|
+
callback = lambda {|*_| events << _.first}
|
35
|
+
ActiveSupport::Notifications.subscribed(callback, name) do
|
36
|
+
ActiveSupport::Notifications.instrument(name)
|
37
|
+
ActiveSupport::Notifications.instrument(name2)
|
38
|
+
ActiveSupport::Notifications.instrument(name)
|
39
|
+
end
|
40
|
+
assert_equal expected, events
|
41
|
+
|
42
|
+
ActiveSupport::Notifications.instrument(name)
|
43
|
+
assert_equal expected, events
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class UnsubscribeTest < TestCase
|
48
|
+
def test_unsubscribing_removes_a_subscription
|
49
|
+
@notifier.publish :foo
|
50
|
+
@notifier.wait
|
51
|
+
assert_equal [[:foo]], @events
|
52
|
+
@notifier.unsubscribe(@subscription)
|
53
|
+
@notifier.publish :foo
|
54
|
+
@notifier.wait
|
55
|
+
assert_equal [[:foo]], @events
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_unsubscribing_by_name_removes_a_subscription
|
59
|
+
@notifier.publish "named.subscription", :foo
|
60
|
+
@notifier.wait
|
61
|
+
assert_equal [["named.subscription", :foo]], @named_events
|
62
|
+
@notifier.unsubscribe("named.subscription")
|
63
|
+
@notifier.publish "named.subscription", :foo
|
64
|
+
@notifier.wait
|
65
|
+
assert_equal [["named.subscription", :foo]], @named_events
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_unsubscribing_by_name_leaves_the_other_subscriptions
|
69
|
+
@notifier.publish "named.subscription", :foo
|
70
|
+
@notifier.wait
|
71
|
+
assert_equal [["named.subscription", :foo]], @events
|
72
|
+
@notifier.unsubscribe("named.subscription")
|
73
|
+
@notifier.publish "named.subscription", :foo
|
74
|
+
@notifier.wait
|
75
|
+
assert_equal [["named.subscription", :foo], ["named.subscription", :foo]], @events
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def event(*args)
|
80
|
+
args
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class SyncPubSubTest < TestCase
|
85
|
+
def test_events_are_published_to_a_listener
|
86
|
+
@notifier.publish :foo
|
87
|
+
@notifier.wait
|
88
|
+
assert_equal [[:foo]], @events
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_publishing_multiple_times_works
|
92
|
+
@notifier.publish :foo
|
93
|
+
@notifier.publish :foo
|
94
|
+
@notifier.wait
|
95
|
+
assert_equal [[:foo], [:foo]], @events
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_publishing_after_a_new_subscribe_works
|
99
|
+
@notifier.publish :foo
|
100
|
+
@notifier.publish :foo
|
101
|
+
|
102
|
+
@notifier.subscribe("not_existant") do |*args|
|
103
|
+
@events << ActiveSupport::Notifications::Event.new(*args)
|
104
|
+
end
|
105
|
+
|
106
|
+
@notifier.publish :foo
|
107
|
+
@notifier.publish :foo
|
108
|
+
@notifier.wait
|
109
|
+
|
110
|
+
assert_equal [[:foo]] * 4, @events
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_log_subscriber_with_string
|
114
|
+
events = []
|
115
|
+
@notifier.subscribe('1') { |*args| events << args }
|
116
|
+
|
117
|
+
@notifier.publish '1'
|
118
|
+
@notifier.publish '1.a'
|
119
|
+
@notifier.publish 'a.1'
|
120
|
+
@notifier.wait
|
121
|
+
|
122
|
+
assert_equal [['1']], events
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_log_subscriber_with_pattern
|
126
|
+
events = []
|
127
|
+
@notifier.subscribe(/\d/) { |*args| events << args }
|
128
|
+
|
129
|
+
@notifier.publish '1'
|
130
|
+
@notifier.publish 'a.1'
|
131
|
+
@notifier.publish '1.a'
|
132
|
+
@notifier.wait
|
133
|
+
|
134
|
+
assert_equal [['1'], ['a.1'], ['1.a']], events
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_multiple_log_subscribers
|
138
|
+
@another = []
|
139
|
+
@notifier.subscribe { |*args| @another << args }
|
140
|
+
@notifier.publish :foo
|
141
|
+
@notifier.wait
|
142
|
+
|
143
|
+
assert_equal [[:foo]], @events
|
144
|
+
assert_equal [[:foo]], @another
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
def event(*args)
|
149
|
+
args
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class InstrumentationTest < TestCase
|
154
|
+
delegate :instrument, :to => ActiveSupport::Notifications
|
155
|
+
|
156
|
+
def test_instrument_returns_block_result
|
157
|
+
assert_equal 2, instrument(:awesome) { 1 + 1 }
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_instrument_yields_the_paylod_for_further_modification
|
161
|
+
assert_equal 2, instrument(:awesome) { |p| p[:result] = 1 + 1 }
|
162
|
+
assert_equal 1, @events.size
|
163
|
+
assert_equal :awesome, @events.first.name
|
164
|
+
assert_equal Hash[:result => 2], @events.first.payload
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_instrumenter_exposes_its_id
|
168
|
+
assert_equal 20, ActiveSupport::Notifications.instrumenter.id.size
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_nested_events_can_be_instrumented
|
172
|
+
instrument(:awesome, :payload => "notifications") do
|
173
|
+
instrument(:wot, :payload => "child") do
|
174
|
+
1 + 1
|
175
|
+
end
|
176
|
+
|
177
|
+
assert_equal 1, @events.size
|
178
|
+
assert_equal :wot, @events.first.name
|
179
|
+
assert_equal Hash[:payload => "child"], @events.first.payload
|
180
|
+
end
|
181
|
+
|
182
|
+
assert_equal 2, @events.size
|
183
|
+
assert_equal :awesome, @events.last.name
|
184
|
+
assert_equal Hash[:payload => "notifications"], @events.last.payload
|
185
|
+
end
|
186
|
+
|
187
|
+
def test_instrument_publishes_when_exception_is_raised
|
188
|
+
begin
|
189
|
+
instrument(:awesome, :payload => "notifications") do
|
190
|
+
raise "FAIL"
|
191
|
+
end
|
192
|
+
rescue RuntimeError => e
|
193
|
+
assert_equal "FAIL", e.message
|
194
|
+
end
|
195
|
+
|
196
|
+
assert_equal 1, @events.size
|
197
|
+
assert_equal Hash[:payload => "notifications",
|
198
|
+
:exception => ["RuntimeError", "FAIL"]], @events.last.payload
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_event_is_pushed_even_without_block
|
202
|
+
instrument(:awesome, :payload => "notifications")
|
203
|
+
assert_equal 1, @events.size
|
204
|
+
assert_equal :awesome, @events.last.name
|
205
|
+
assert_equal Hash[:payload => "notifications"], @events.last.payload
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
class EventTest < TestCase
|
210
|
+
def test_events_are_initialized_with_details
|
211
|
+
time = Time.now
|
212
|
+
event = event(:foo, time, time + 0.01, random_id, {})
|
213
|
+
|
214
|
+
assert_equal :foo, event.name
|
215
|
+
assert_equal time, event.time
|
216
|
+
assert_in_delta 10.0, event.duration, 0.00001
|
217
|
+
end
|
218
|
+
|
219
|
+
def test_events_consumes_information_given_as_payload
|
220
|
+
event = event(:foo, Time.now, Time.now + 1, random_id, :payload => :bar)
|
221
|
+
assert_equal Hash[:payload => :bar], event.payload
|
222
|
+
end
|
223
|
+
|
224
|
+
def test_event_is_parent_based_on_time_frame
|
225
|
+
time = Time.utc(2009, 01, 01, 0, 0, 1)
|
226
|
+
|
227
|
+
parent = event(:foo, Time.utc(2009), Time.utc(2009) + 100, random_id, {})
|
228
|
+
child = event(:foo, time, time + 10, random_id, {})
|
229
|
+
not_child = event(:foo, time, time + 100, random_id, {})
|
230
|
+
|
231
|
+
assert parent.parent_of?(child)
|
232
|
+
assert !child.parent_of?(parent)
|
233
|
+
assert !parent.parent_of?(not_child)
|
234
|
+
assert !not_child.parent_of?(parent)
|
235
|
+
end
|
236
|
+
|
237
|
+
protected
|
238
|
+
def random_id
|
239
|
+
@random_id ||= SecureRandom.hex(10)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activesupport_notifications_backport
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Rick Olson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-20 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: &70341193016060 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.3.14
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70341193016060
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &70341193015680 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70341193015680
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: mocha
|
38
|
+
requirement: &70341193015220 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70341193015220
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: test-unit
|
49
|
+
requirement: &70341193014800 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70341193014800
|
58
|
+
description: ActiveSupport::Notifications backported for Rails 2.3
|
59
|
+
email: technoweenie@gmail.com
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- Rakefile
|
65
|
+
- activesupport_notifications_backport.gemspec
|
66
|
+
- lib/active_support/log_subscriber.rb
|
67
|
+
- lib/active_support/log_subscriber/test_helper.rb
|
68
|
+
- lib/active_support/notifications.rb
|
69
|
+
- lib/active_support/notifications/fanout.rb
|
70
|
+
- lib/active_support/notifications/instrumenter.rb
|
71
|
+
- lib/activesupport_notifications_backport.rb
|
72
|
+
- test/abstract_unit.rb
|
73
|
+
- test/log_subscriber_test.rb
|
74
|
+
- test/notifications_test.rb
|
75
|
+
homepage: http://github.com/technoweenie/activesupport_notifications_backport
|
76
|
+
licenses: []
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 1.3.5
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project: activesupport_notifications_backport
|
95
|
+
rubygems_version: 1.8.11
|
96
|
+
signing_key:
|
97
|
+
specification_version: 2
|
98
|
+
summary: ActiveSupport::Notifications backported for Rails 2.3
|
99
|
+
test_files:
|
100
|
+
- test/abstract_unit.rb
|
101
|
+
- test/log_subscriber_test.rb
|
102
|
+
- test/notifications_test.rb
|