activesupport_notifications_backport 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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