activesupport_notifications_backport 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|