hoptoad_notifier 2.4.0 → 2.4.10

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.
@@ -7,7 +7,8 @@ module HoptoadNotifier
7
7
  :http_open_timeout, :http_read_timeout, :ignore, :ignore_by_filters,
8
8
  :ignore_user_agent, :notifier_name, :notifier_url, :notifier_version,
9
9
  :params_filters, :project_root, :port, :protocol, :proxy_host,
10
- :proxy_pass, :proxy_port, :proxy_user, :secure, :framework, :js_notifier].freeze
10
+ :proxy_pass, :proxy_port, :proxy_user, :secure, :framework,
11
+ :user_information].freeze
11
12
 
12
13
  # The API key for your project, found on the project edit form.
13
14
  attr_accessor :api_key
@@ -62,9 +63,6 @@ module HoptoadNotifier
62
63
  # +true+ if you want to check for production errors matching development errors, +false+ otherwise.
63
64
  attr_accessor :development_lookup
64
65
 
65
- # +true+ if you want to enable the JavaScript notifier in production environments
66
- attr_accessor :js_notifier
67
-
68
66
  # The name of the environment the application is running in
69
67
  attr_accessor :environment_name
70
68
 
@@ -83,6 +81,9 @@ module HoptoadNotifier
83
81
  # The logger used by HoptoadNotifier
84
82
  attr_accessor :logger
85
83
 
84
+ # The text that the placeholder is replaced with. {{error_id}} is the actual error number.
85
+ attr_accessor :user_information
86
+
86
87
  # The framework HoptoadNotifier is configured to use
87
88
  attr_accessor :framework
88
89
 
@@ -128,11 +129,11 @@ module HoptoadNotifier
128
129
  @ignore_user_agent = []
129
130
  @development_environments = %w(development test cucumber)
130
131
  @development_lookup = true
131
- @js_notifier = false
132
132
  @notifier_name = 'Hoptoad Notifier'
133
133
  @notifier_version = VERSION
134
134
  @notifier_url = 'http://hoptoadapp.com'
135
135
  @framework = 'Standalone'
136
+ @user_information = 'Hoptoad Error {{error_id}}'
136
137
  end
137
138
 
138
139
  # Takes a block and adds it to the list of backtrace filters. When the filters
@@ -217,6 +218,10 @@ module HoptoadNotifier
217
218
  end
218
219
  end
219
220
 
221
+ def js_notifier=(*args)
222
+ warn '[HOPTOAD] config.js_notifier has been deprecated and has no effect. You should use <%= hoptoad_javascript_notifier %> directly at the top of your layouts. Be sure to place it before all other javascript.'
223
+ end
224
+
220
225
  def environment_filters
221
226
  warn 'config.environment_filters has been deprecated and has no effect.'
222
227
  []
@@ -226,14 +226,16 @@ module HoptoadNotifier
226
226
  # Removes non-serializable data. Allowed data types are strings, arrays,
227
227
  # and hashes. All other types are converted to strings.
228
228
  # TODO: move this onto Hash
229
- def clean_unserializable_data(data)
229
+ def clean_unserializable_data(data, stack = [])
230
+ return "[possible infinite recursion halted]" if stack.any?{|item| item == data.object_id }
231
+
230
232
  if data.respond_to?(:to_hash)
231
233
  data.to_hash.inject({}) do |result, (key, value)|
232
- result.merge(key => clean_unserializable_data(value))
234
+ result.merge(key => clean_unserializable_data(value, stack + [data.object_id]))
233
235
  end
234
236
  elsif data.respond_to?(:to_ary)
235
237
  data.collect do |value|
236
- clean_unserializable_data(value)
238
+ clean_unserializable_data(value, stack + [data.object_id])
237
239
  end
238
240
  else
239
241
  data.to_s
@@ -26,12 +26,14 @@ module HoptoadNotifier
26
26
  begin
27
27
  response = @app.call(env)
28
28
  rescue Exception => raised
29
- HoptoadNotifier.notify_or_ignore(raised, :rack_env => env)
29
+ error_id = HoptoadNotifier.notify_or_ignore(raised, :rack_env => env)
30
+ env['hoptoad.error_id'] = error_id
30
31
  raise
31
32
  end
32
33
 
33
34
  if env['rack.exception']
34
- HoptoadNotifier.notify_or_ignore(env['rack.exception'], :rack_env => env)
35
+ error_id = HoptoadNotifier.notify_or_ignore(env['rack.exception'], :rack_env => env)
36
+ env['hoptoad.error_id'] = error_id
35
37
  end
36
38
 
37
39
  response
@@ -14,7 +14,8 @@ module HoptoadNotifier
14
14
  # any custom processing that is defined with Rails 2's exception helpers.
15
15
  def rescue_action_in_public_with_hoptoad(exception)
16
16
  unless hoptoad_ignore_user_agent?
17
- HoptoadNotifier.notify_or_ignore(exception, hoptoad_request_data)
17
+ error_id = HoptoadNotifier.notify_or_ignore(exception, hoptoad_request_data)
18
+ request.env['hoptoad.error_id'] = error_id
18
19
  end
19
20
  rescue_action_in_public_without_hoptoad(exception)
20
21
  end
@@ -2,41 +2,41 @@ module HoptoadNotifier
2
2
  module Rails
3
3
  module JavascriptNotifier
4
4
  def self.included(base) #:nodoc:
5
- base.send(:after_filter, :insert_hoptoad_javascript_notifier)
5
+ base.send :helper_method, :hoptoad_javascript_notifier
6
6
  end
7
7
 
8
8
  private
9
9
 
10
- def insert_hoptoad_javascript_notifier
10
+ def hoptoad_javascript_notifier
11
11
  return unless HoptoadNotifier.configuration.public?
12
- return unless HoptoadNotifier.configuration.js_notifier
13
12
 
14
- path = File.join(File.dirname(__FILE__), '..', '..', 'templates', 'javascript_notifier.erb')
13
+ path = File.join File.dirname(__FILE__), '..', '..', 'templates', 'javascript_notifier.erb'
15
14
  host = HoptoadNotifier.configuration.host.dup
16
15
  port = HoptoadNotifier.configuration.port
17
16
  host << ":#{port}" unless [80, 443].include?(port)
18
17
 
19
- options = {
20
- :file => path,
21
- :layout => false,
22
- :use_full_path => false,
23
- :locals => {
24
- :host => host,
25
- :api_key => HoptoadNotifier.configuration.api_key,
26
- :environment => HoptoadNotifier.configuration.environment_name
18
+ options = {
19
+ :file => path,
20
+ :layout => false,
21
+ :use_full_path => false,
22
+ :locals => {
23
+ :host => host,
24
+ :api_key => HoptoadNotifier.configuration.api_key,
25
+ :environment => HoptoadNotifier.configuration.environment_name,
26
+ :action_name => action_name,
27
+ :controller_name => controller_name,
28
+ :url => request.url
27
29
  }
28
30
  }
29
31
 
30
32
  if @template
31
- javascript = @template.render(options)
33
+ @template.render(options)
32
34
  else
33
- javascript = render_to_string(options)
35
+ render_to_string(options)
34
36
  end
35
37
 
36
- if response.body.respond_to?(:gsub)
37
- response.body = response.body.gsub(/<(head.*?)>/i, "<\1>\n" + javascript)
38
- end
39
38
  end
39
+
40
40
  end
41
41
  end
42
42
  end
@@ -23,6 +23,8 @@ module HoptoadNotifier
23
23
  if defined?(::Rails.configuration) && ::Rails.configuration.respond_to?(:middleware)
24
24
  ::Rails.configuration.middleware.insert_after 'ActionController::Failsafe',
25
25
  HoptoadNotifier::Rack
26
+ ::Rails.configuration.middleware.insert_after 'Rack::Lock',
27
+ HoptoadNotifier::UserInformer
26
28
  end
27
29
 
28
30
  HoptoadNotifier.configure(true) do |config|
@@ -9,6 +9,7 @@ module HoptoadNotifier
9
9
 
10
10
  initializer "hoptoad.use_rack_middleware" do |app|
11
11
  app.config.middleware.use "HoptoadNotifier::Rack"
12
+ app.config.middleware.insert 0, "HoptoadNotifier::UserInformer"
12
13
  end
13
14
 
14
15
  config.after_initialize do
@@ -46,6 +46,11 @@ module HoptoadNotifier
46
46
  else
47
47
  log :error, "Failure: #{response.class}", response
48
48
  end
49
+
50
+ if response && response.respond_to?(:body)
51
+ error_id = response.body.match(%r{<error-id[^>]*>(.*?)</error-id>})
52
+ error_id[1] if error_id
53
+ end
49
54
  end
50
55
 
51
56
  private
@@ -6,7 +6,13 @@ namespace :hoptoad do
6
6
  :scm_revision => ENV['REVISION'],
7
7
  :scm_repository => ENV['REPO'],
8
8
  :local_username => ENV['USER'],
9
- :api_key => ENV['API_KEY'])
9
+ :api_key => ENV['API_KEY'],
10
+ :dry_run => ENV['DRY_RUN'])
11
+ end
12
+
13
+ task :log_stdout do
14
+ require 'logger'
15
+ RAILS_DEFAULT_LOGGER = Logger.new(STDOUT)
10
16
  end
11
17
 
12
18
  namespace :heroku do
@@ -8,7 +8,7 @@ namespace :hoptoad do
8
8
 
9
9
  require 'action_controller/test_process'
10
10
 
11
- Dir["app/controllers/application*.rb"].each { |file| require(file) }
11
+ Dir["app/controllers/application*.rb"].each { |file| require(File.expand_path(file)) }
12
12
 
13
13
  class HoptoadTestingException < RuntimeError; end
14
14
 
@@ -63,9 +63,9 @@ namespace :hoptoad do
63
63
 
64
64
  def exception_class
65
65
  exception_name = ENV['EXCEPTION'] || "HoptoadTestingException"
66
- Object.const_get(exception_name)
66
+ exception_name.split("::").inject(Object){|klass, name| klass.const_get(name)}
67
67
  rescue
68
- Object.const_set(exception_name, Class.new(Exception))
68
+ Object.const_set(exception_name.gsub(/:+/, "_"), Class.new(Exception))
69
69
  end
70
70
 
71
71
  def logger
@@ -0,0 +1,25 @@
1
+ module HoptoadNotifier
2
+ class UserInformer
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def replacement(with)
8
+ @replacement ||= HoptoadNotifier.configuration.user_information.gsub(/\{\{\s*error_id\s*\}\}/, with.to_s)
9
+ end
10
+
11
+ def call(env)
12
+ status, headers, body = @app.call(env)
13
+ if env['hoptoad.error_id'] && HoptoadNotifier.configuration.user_information
14
+ new_body = []
15
+ body.each do |chunk|
16
+ new_body << chunk.gsub("<!-- HOPTOAD ERROR -->", replacement(env['hoptoad.error_id']))
17
+ end
18
+ headers['Content-Length'] = new_body.sum(&:length).to_s
19
+ body = new_body
20
+ end
21
+ [status, headers, body]
22
+ end
23
+ end
24
+ end
25
+
@@ -1,3 +1,3 @@
1
1
  module HoptoadNotifier
2
- VERSION = "2.4.0".freeze
2
+ VERSION = "2.4.9".freeze
3
3
  end
@@ -12,6 +12,7 @@ require 'hoptoad_notifier/notice'
12
12
  require 'hoptoad_notifier/sender'
13
13
  require 'hoptoad_notifier/backtrace'
14
14
  require 'hoptoad_notifier/rack'
15
+ require 'hoptoad_notifier/user_informer'
15
16
 
16
17
  require 'hoptoad_notifier/railtie' if defined?(Rails::Railtie)
17
18
 
data/lib/hoptoad_tasks.rb CHANGED
@@ -24,6 +24,7 @@ module HoptoadTasks
24
24
  return false
25
25
  end
26
26
 
27
+ dry_run = opts.delete(:dry_run)
27
28
  params = {'api_key' => opts.delete(:api_key) ||
28
29
  HoptoadNotifier.configuration.api_key}
29
30
  opts.each {|k,v| params["deploy[#{k}]"] = v }
@@ -35,10 +36,15 @@ module HoptoadTasks
35
36
  HoptoadNotifier.configuration.proxy_user,
36
37
  HoptoadNotifier.configuration.proxy_pass)
37
38
 
38
- response = proxy.post_form(url, params)
39
+ if dry_run
40
+ puts url, params.inspect
41
+ return true
42
+ else
43
+ response = proxy.post_form(url, params)
39
44
 
40
- puts response.body
41
- return Net::HTTPSuccess === response
45
+ puts response.body
46
+ return Net::HTTPSuccess === response
47
+ end
42
48
  end
43
49
  end
44
50
 
@@ -38,7 +38,7 @@ class HoptoadGenerator < Rails::Generators::Base
38
38
  if File.exists?('config/deploy.rb') && File.exists?('Capfile')
39
39
  append_file('config/deploy.rb', <<-HOOK)
40
40
 
41
- require 'config/boot'
41
+ require './config/boot'
42
42
  require 'hoptoad_notifier/capistrano'
43
43
  HOOK
44
44
  end
@@ -1,17 +1,13 @@
1
- <script type="text/javascript">
2
- //<![CDATA[
3
- var notifierJsScheme = (("https:" == document.location.protocol) ? "https://" : "http://");
4
- document.write(unescape("%3Cscript src='" + notifierJsScheme + "<%= host %>/javascripts/notifier.js' type='text/javascript'%3E%3C/script%3E"));
5
- //]]>
6
- </script>
1
+ <%= javascript_tag %Q{
2
+ var notifierJsScheme = (("https:" == document.location.protocol) ? "https://" : "http://");
3
+ document.write(unescape("%3Cscript src='" + notifierJsScheme + "#{host}/javascripts/notifier.js' type='text/javascript'%3E%3C/script%3E"));
4
+ }
5
+ %>
7
6
 
8
- <script type="text/javascript">
9
- Hoptoad.setKey('<%= api_key %>');
10
- Hoptoad.setHost('<%= host %>');
11
- Hoptoad.setEnvironment('<%= environment %>');
12
- Hoptoad.setErrorDefaults({
13
- url: "<%= a = "#{controller.request.protocol}#{controller.request.host_with_port}#{controller.request.request_uri}"; p a; a %>",
14
- component: "<%= controller.controller_name %>",
15
- action: "<%= controller.action_name %>"
16
- });
17
- </script>
7
+ <%= javascript_tag %Q{
8
+ Hoptoad.setKey('#{api_key}');
9
+ Hoptoad.setHost('#{host}');
10
+ Hoptoad.setEnvironment('#{environment}');
11
+ Hoptoad.setErrorDefaults({ url: "#{escape_javascript url}", component: "#{controller_name}", action: "#{action_name}" });
12
+ }
13
+ %>
@@ -20,6 +20,25 @@ class BacktraceTest < Test::Unit::TestCase
20
20
  assert_equal 'app/controllers/users_controller.rb', line.file
21
21
  assert_equal 'index', line.method
22
22
  end
23
+
24
+ should "parse a windows backtrace into lines" do
25
+ array = [
26
+ "C:/Program Files/Server/app/models/user.rb:13:in `magic'",
27
+ "C:/Program Files/Server/app/controllers/users_controller.rb:8:in `index'"
28
+ ]
29
+
30
+ backtrace = HoptoadNotifier::Backtrace.parse(array)
31
+
32
+ line = backtrace.lines.first
33
+ assert_equal '13', line.number
34
+ assert_equal 'C:/Program Files/Server/app/models/user.rb', line.file
35
+ assert_equal 'magic', line.method
36
+
37
+ line = backtrace.lines.last
38
+ assert_equal '8', line.number
39
+ assert_equal 'C:/Program Files/Server/app/controllers/users_controller.rb', line.file
40
+ assert_equal 'index', line.method
41
+ end
23
42
 
24
43
  should "be equal with equal lines" do
25
44
  one = build_backtrace_array
data/test/catcher_test.rb CHANGED
@@ -25,6 +25,8 @@ class ActionControllerCatcherTest < Test::Unit::TestCase
25
25
 
26
26
  def assert_sent_hash(hash, xpath)
27
27
  hash.each do |key, value|
28
+ next if key.match(/^hoptoad\./) # We added this key.
29
+
28
30
  element_xpath = "#{xpath}/var[@key = '#{key}']"
29
31
  if value.respond_to?(:to_hash)
30
32
  assert_sent_hash value.to_hash, element_xpath
@@ -29,7 +29,6 @@ class ConfigurationTest < Test::Unit::TestCase
29
29
  HoptoadNotifier::Configuration::IGNORE_DEFAULT
30
30
  assert_config_default :development_lookup, true
31
31
  assert_config_default :framework, 'Standalone'
32
- assert_config_default :js_notifier, false
33
32
  end
34
33
 
35
34
  should "provide default values for secure connections" do
@@ -85,7 +84,7 @@ class ConfigurationTest < Test::Unit::TestCase
85
84
  :http_read_timeout, :ignore, :ignore_by_filters, :ignore_user_agent,
86
85
  :notifier_name, :notifier_url, :notifier_version, :params_filters,
87
86
  :project_root, :port, :protocol, :proxy_host, :proxy_pass, :proxy_port,
88
- :proxy_user, :secure, :development_lookup, :js_notifier].each do |option|
87
+ :proxy_user, :secure, :development_lookup].each do |option|
89
88
  assert_equal config[option], hash[option], "Wrong value for #{option}"
90
89
  end
91
90
  end
@@ -108,6 +107,14 @@ class ConfigurationTest < Test::Unit::TestCase
108
107
  assert_equal [], config.environment_filters
109
108
  end
110
109
 
110
+ should "warn when attempting to write js_notifier" do
111
+ config = HoptoadNotifier::Configuration.new
112
+ config.
113
+ expects(:warn).
114
+ with(regexp_matches(/deprecated/i))
115
+ config.js_notifier = true
116
+ end
117
+
111
118
  should "allow ignored user agents to be appended" do
112
119
  assert_appends_value :ignore_user_agent
113
120
  end
data/test/helper.rb CHANGED
@@ -7,10 +7,12 @@ gem "actionpack", "= 2.3.8"
7
7
  gem "nokogiri", "= 1.4.3.1"
8
8
  gem "shoulda", "= 2.11.3"
9
9
  gem 'bourne', '>= 1.0'
10
+ gem "sham_rack", "~> 1.3.0"
10
11
 
11
12
  $LOAD_PATH << File.join(File.dirname(__FILE__), *%w[.. vendor ginger lib])
12
13
  $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
13
14
 
15
+ require 'thread'
14
16
  require 'shoulda'
15
17
  require 'mocha'
16
18
 
@@ -25,6 +27,7 @@ require 'active_support'
25
27
  require 'nokogiri'
26
28
  require 'rack'
27
29
  require 'bourne'
30
+ require 'sham_rack'
28
31
 
29
32
  require "hoptoad_notifier"
30
33
 
@@ -49,7 +49,18 @@ class HoptoadTasksTest < Test::Unit::TestCase
49
49
  HoptoadNotifier.configuration.proxy_pass).
50
50
  returns(@http_proxy)
51
51
 
52
- @options = { :rails_env => "staging" }
52
+ @options = { :rails_env => "staging", :dry_run => false }
53
+ end
54
+
55
+ context "performing a dry run" do
56
+ setup { @output = HoptoadTasks.deploy(@options.merge(:dry_run => true)) }
57
+
58
+ should "return true without performing any actual request" do
59
+ assert_equal true, @output
60
+ assert_received(@http_proxy, :post_form) do|expects|
61
+ expects.never
62
+ end
63
+ end
53
64
  end
54
65
 
55
66
  context "on deploy(options)" do
@@ -0,0 +1,52 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+ require 'hoptoad_notifier/rails/javascript_notifier'
3
+ require 'ostruct'
4
+
5
+ class JavascriptNotifierTest < Test::Unit::TestCase
6
+ module FakeRenderer
7
+ def javascript_tag(text)
8
+ "<script>#{text}</script>"
9
+ end
10
+ def escape_javascript(text)
11
+ "ESC#{text}ESC"
12
+ end
13
+ end
14
+
15
+ class FakeController
16
+ def self.helper_method(*args)
17
+ end
18
+
19
+ include HoptoadNotifier::Rails::JavascriptNotifier
20
+
21
+ def action_name
22
+ "action"
23
+ end
24
+
25
+ def controller_name
26
+ "controller"
27
+ end
28
+
29
+ def request
30
+ @request ||= OpenStruct.new
31
+ end
32
+
33
+ def render_to_string(options)
34
+ context = OpenStruct.new(options[:locals])
35
+ context.extend(FakeRenderer)
36
+ context.instance_eval do
37
+ erb = ERB.new(IO.read(options[:file]))
38
+ erb.result(binding)
39
+ end
40
+ end
41
+ end
42
+
43
+ should "make sure escape_javacript is called on the request.url" do
44
+ HoptoadNotifier.configure do
45
+ end
46
+ controller = FakeController.new
47
+ controller.request.url = "bad_javascript"
48
+ assert controller.send(:hoptoad_javascript_notifier)['"ESCbad_javascriptESC"']
49
+ assert ! controller.send(:hoptoad_javascript_notifier)['"bad_javascript"']
50
+ end
51
+ end
52
+
@@ -0,0 +1,10 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class RecursionTest < Test::Unit::TestCase
4
+ should "not allow infinite recursion" do
5
+ hash = {:a => :a}
6
+ hash[:hash] = hash
7
+ notice = HoptoadNotifier::Notice.new(:parameters => hash)
8
+ assert_equal "[possible infinite recursion halted]", notice.parameters[:hash]
9
+ end
10
+ end
data/test/sender_test.rb CHANGED
@@ -16,11 +16,10 @@ class SenderTest < Test::Unit::TestCase
16
16
  notice = args.delete(:notice) || build_notice_data
17
17
  sender = args.delete(:sender) || build_sender(args)
18
18
  sender.send_to_hoptoad(notice)
19
- sender
20
19
  end
21
20
 
22
- def stub_http
23
- response = stub(:body => 'body')
21
+ def stub_http(options = {})
22
+ response = stub(:body => options[:body] || 'body')
24
23
  http = stub(:post => response,
25
24
  :read_timeout= => nil,
26
25
  :open_timeout= => nil,
@@ -58,6 +57,17 @@ class SenderTest < Test::Unit::TestCase
58
57
  end
59
58
  end
60
59
 
60
+ should "return the created group's id on successful posting" do
61
+ http = stub_http(:body => '<error-id type="integer">3799307</error-id>')
62
+ assert_equal "3799307", send_exception(:secure => false)
63
+ end
64
+
65
+ should "return nil on failed posting" do
66
+ http = stub_http
67
+ http.stubs(:post).raises(Errno::ECONNREFUSED)
68
+ assert_equal nil, send_exception(:secure => false)
69
+ end
70
+
61
71
  should "not fail when posting and a timeout exception occurs" do
62
72
  http = stub_http
63
73
  http.stubs(:post).raises(TimeoutError)
@@ -0,0 +1,29 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class UserInformerTest < Test::Unit::TestCase
4
+ should "modify output if there is a hoptoad id" do
5
+ main_app = lambda do |env|
6
+ env['hoptoad.error_id'] = 1
7
+ [200, {}, ["<!-- HOPTOAD ERROR -->"]]
8
+ end
9
+ informer_app = HoptoadNotifier::UserInformer.new(main_app)
10
+
11
+ ShamRack.mount(informer_app, "example.com")
12
+
13
+ response = Net::HTTP.get_response(URI.parse("http://example.com/"))
14
+ assert_equal "Hoptoad Error 1", response.body
15
+ assert_equal 15, response["Content-Length"].to_i
16
+ end
17
+
18
+ should "not modify output if there is no hoptoad id" do
19
+ main_app = lambda do |env|
20
+ [200, {}, ["<!-- HOPTOAD ERROR -->"]]
21
+ end
22
+ informer_app = HoptoadNotifier::UserInformer.new(main_app)
23
+
24
+ ShamRack.mount(informer_app, "example.com")
25
+
26
+ response = Net::HTTP.get_response(URI.parse("http://example.com/"))
27
+ assert_equal "<!-- HOPTOAD ERROR -->", response.body
28
+ end
29
+ end