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.
@@ -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,5 @@
1
+ require 'test/unit'
2
+ require 'mocha'
3
+ require 'active_support'
4
+ require 'active_support/test_case'
5
+ require File.expand_path('../../lib/activesupport_notifications_backport', __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