honeybadger 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/Appraisals +20 -0
  2. data/Gemfile +1 -13
  3. data/Gemfile.lock +39 -64
  4. data/MIT-LICENSE +5 -0
  5. data/README.md +102 -3
  6. data/Rakefile +9 -134
  7. data/features/metal.feature +6 -4
  8. data/features/rack.feature +3 -4
  9. data/features/rails.feature +106 -99
  10. data/features/rake.feature +0 -2
  11. data/features/sinatra.feature +1 -3
  12. data/features/step_definitions/metal_steps.rb +2 -1
  13. data/features/step_definitions/rack_steps.rb +2 -2
  14. data/features/step_definitions/rails_steps.rb +238 -0
  15. data/features/step_definitions/rake_steps.rb +1 -1
  16. data/features/support/env.rb +7 -7
  17. data/features/support/rails.rb +41 -58
  18. data/gemfiles/rails2.3.gemfile +9 -0
  19. data/gemfiles/rails2.3.gemfile.lock +102 -0
  20. data/gemfiles/rails3.0.gemfile +8 -0
  21. data/gemfiles/rails3.0.gemfile.lock +146 -0
  22. data/gemfiles/rails3.1.gemfile +8 -0
  23. data/gemfiles/rails3.1.gemfile.lock +161 -0
  24. data/gemfiles/rails3.2.gemfile +8 -0
  25. data/gemfiles/rails3.2.gemfile.lock +160 -0
  26. data/honeybadger.gemspec +30 -24
  27. data/lib/honeybadger.rb +117 -113
  28. data/lib/honeybadger/backtrace.rb +9 -3
  29. data/lib/honeybadger/configuration.rb +22 -0
  30. data/lib/honeybadger/notice.rb +9 -0
  31. data/lib/honeybadger/rack.rb +5 -4
  32. data/lib/honeybadger/rails3_tasks.rb +20 -23
  33. data/lib/honeybadger/sender.rb +10 -4
  34. data/lib/honeybadger/shared_tasks.rb +12 -1
  35. data/lib/honeybadger/tasks.rb +6 -1
  36. data/test/test_helper.rb +71 -71
  37. data/test/unit/backtrace_test.rb +26 -23
  38. data/test/unit/capistrano_test.rb +1 -1
  39. data/test/unit/configuration_test.rb +19 -3
  40. data/test/unit/honeybadger_tasks_test.rb +1 -1
  41. data/test/unit/logger_test.rb +1 -1
  42. data/test/unit/notice_test.rb +71 -16
  43. data/test/unit/notifier_test.rb +25 -5
  44. data/test/unit/rack_test.rb +21 -1
  45. data/test/unit/rails/action_controller_catcher_test.rb +1 -1
  46. data/test/unit/rails_test.rb +1 -1
  47. data/test/unit/sender_test.rb +1 -1
  48. metadata +69 -45
  49. data/SUPPORTED_RAILS_VERSIONS +0 -37
  50. data/TESTING.md +0 -33
  51. data/features/step_definitions/file_steps.rb +0 -10
  52. data/features/step_definitions/rails_application_steps.rb +0 -394
  53. data/features/support/terminal.rb +0 -107
@@ -4,7 +4,7 @@ module Honeybadger
4
4
 
5
5
  # Public: Handles backtrace parsing line by line
6
6
  class Line
7
- # regexp (optionnally allowing leading X: for windows support)
7
+ # regexp (optionally allowing leading X: for windows support)
8
8
  INPUT_FORMAT = %r{^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$}.freeze
9
9
 
10
10
  # Public: The file portion of the line (such as app/models/user.rb)
@@ -62,6 +62,11 @@ module Honeybadger
62
62
  "<Line:#{to_s}>"
63
63
  end
64
64
 
65
+ # Public: Determines if this line is part of the application trace or not
66
+ def application?
67
+ (filtered_file =~ /^\[PROJECT_ROOT\]/i) && !(filtered_file =~ /^\[PROJECT_ROOT\]\/vendor/i)
68
+ end
69
+
65
70
  # Public: An excerpt from the source file, lazily loaded to preserve
66
71
  # performance
67
72
  def source(radius = 2)
@@ -94,7 +99,7 @@ module Honeybadger
94
99
  end
95
100
 
96
101
  # Public: holder for an Array of Backtrace::Line instances
97
- attr_reader :lines
102
+ attr_reader :lines, :application_lines
98
103
 
99
104
  def self.parse(ruby_backtrace, opts = {})
100
105
  ruby_lines = split_multiline_backtrace(ruby_backtrace)
@@ -108,6 +113,7 @@ module Honeybadger
108
113
 
109
114
  def initialize(lines)
110
115
  self.lines = lines
116
+ self.application_lines = lines.select(&:application?)
111
117
  end
112
118
 
113
119
  # Public
@@ -146,7 +152,7 @@ module Honeybadger
146
152
 
147
153
  private
148
154
 
149
- attr_writer :lines
155
+ attr_writer :lines, :application_lines
150
156
 
151
157
  def self.split_multiline_backtrace(backtrace)
152
158
  if backtrace.to_a.size == 1
@@ -91,6 +91,9 @@ module Honeybadger
91
91
  # The radius around trace line to include in source excerpt
92
92
  attr_accessor :source_extract_radius
93
93
 
94
+ # A Proc object used to send notices asynchronously
95
+ attr_writer :async
96
+
94
97
  DEFAULT_PARAMS_FILTERS = %w(password password_confirmation).freeze
95
98
 
96
99
  DEFAULT_BACKTRACE_FILTERS = [
@@ -231,6 +234,25 @@ module Honeybadger
231
234
  !development_environments.include?(environment_name)
232
235
  end
233
236
 
237
+ # Public: Configure async delivery
238
+ #
239
+ # block - An optional block containing an async handler
240
+ #
241
+ # Examples
242
+ #
243
+ # config.async = Proc.new { |notice| Thread.new { notice.deliver } }
244
+ #
245
+ # config.async do |notice|
246
+ # Thread.new { notice.deliver }
247
+ # end
248
+ #
249
+ # Returns configured async handler (should respond to #call(notice))
250
+ def async
251
+ @async = Proc.new if block_given?
252
+ @async
253
+ end
254
+ alias :async? :async
255
+
234
256
  def port
235
257
  @port || default_port
236
258
  end
@@ -114,6 +114,13 @@ module Honeybadger
114
114
  set_context
115
115
  end
116
116
 
117
+ # Public: Send the notice to Honeybadger using the configured sender
118
+ #
119
+ # Returns a reference to the error in Honeybadger
120
+ def deliver
121
+ Honeybadger.sender.send_to_honeybadger(to_json)
122
+ end
123
+
117
124
  # Public: Template used to create JSON payload
118
125
  #
119
126
  # Returns JSON representation of notice
@@ -274,6 +281,8 @@ module Honeybadger
274
281
  parts = line.split(': ')
275
282
  [parts[0].strip, parts[1] || '']
276
283
  end]
284
+ elsif backtrace.application_lines.any?
285
+ backtrace.application_lines.first.source(source_extract_radius)
277
286
  else
278
287
  backtrace.lines.first.source(source_extract_radius)
279
288
  end
@@ -34,21 +34,22 @@ module Honeybadger
34
34
  end
35
35
 
36
36
  def notify_honeybadger(exception,env)
37
- Honeybadger.notify_or_ignore(exception,:rack_env => env) unless ignored_user_agent?(env)
37
+ Honeybadger.notify_or_ignore(exception, :rack_env => env) unless ignored_user_agent?(env)
38
38
  end
39
39
 
40
40
  def call(env)
41
41
  begin
42
42
  response = @app.call(env)
43
43
  rescue Exception => raised
44
- env['honeybadger.error_id'] = notify_honeybadger(raised,env)
44
+ env['honeybadger.error_id'] = notify_honeybadger(raised, env)
45
45
  raise
46
46
  ensure
47
47
  Honeybadger.context.clear!
48
48
  end
49
49
 
50
- if env['rack.exception']
51
- env['honeybadger.error_id'] = notify_honeybadger(env['rack.exception'],env)
50
+ framework_exception = env['rack.exception'] || env['sinatra.error']
51
+ if framework_exception
52
+ env['honeybadger.error_id'] = notify_honeybadger(framework_exception, env)
52
53
  end
53
54
 
54
55
  response
@@ -3,16 +3,26 @@ require File.join(File.dirname(__FILE__), 'shared_tasks')
3
3
 
4
4
  namespace :honeybadger do
5
5
  desc "Verify your gem installation by sending a test exception to the honeybadger service"
6
- task :test => [:environment] do
7
- Rails.logger = defined?(ActiveSupport::TaggedLogging) ?
8
- ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) :
9
- Logger.new(STDOUT)
6
+ task :test => :environment do
7
+ Rails.logger = if defined?(ActiveSupport::TaggedLogging)
8
+ ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
9
+ else
10
+ Logger.new(STDOUT)
11
+ end
12
+ Rails.logger.level = Logger::INFO
10
13
 
11
- Rails.logger.level = Logger::DEBUG
12
14
  Honeybadger.configure(true) do |config|
13
15
  config.logger = Rails.logger
14
16
  end
15
17
 
18
+ # Suppress error logging in Rails' exception handling middleware. Rails 3.0
19
+ # uses ActionDispatch::ShowExceptions to rescue/show exceptions, but does
20
+ # not log anything but application trace. Rails 3.2 now falls back to
21
+ # logging the framework trace (moved to ActionDispatch::DebugExceptions),
22
+ # which caused cluttered output while running the test task.
23
+ class ActionDispatch::DebugExceptions ; def logger(*args) ; @logger ||= Logger.new('/dev/null') ; end ; end
24
+ class ActionDispatch::ShowExceptions ; def logger(*args) ; @logger ||= Logger.new('/dev/null') ; end ; end
25
+
16
26
  require './app/controllers/application_controller'
17
27
 
18
28
  class HoneybadgerTestingException < RuntimeError; end
@@ -22,6 +32,11 @@ namespace :honeybadger do
22
32
  exit
23
33
  end
24
34
 
35
+ if Honeybadger.configuration.async?
36
+ puts "Temporarily disabling asynchronous delivery"
37
+ Honeybadger.configuration.async = nil
38
+ end
39
+
25
40
  Honeybadger.configuration.development_environments = []
26
41
 
27
42
  puts "Configuration:"
@@ -43,35 +58,17 @@ namespace :honeybadger do
43
58
  raise exception_class.new, 'Testing honeybadger via "rake honeybadger:test". If you can see this, it works.'
44
59
  end
45
60
 
46
- # def rescue_action(exception)
47
- # rescue_action_in_public exception
48
- # end
49
-
50
61
  # Ensure we actually have an action to go to.
51
62
  def verify; end
52
63
 
53
- # def consider_all_requests_local
54
- # false
55
- # end
56
-
57
- # def local_request?
58
- # false
59
- # end
60
-
61
64
  def exception_class
62
65
  exception_name = ENV['EXCEPTION'] || "HoneybadgerTestingException"
63
66
  Object.const_get(exception_name)
64
67
  rescue
65
68
  Object.const_set(exception_name, Class.new(Exception))
66
69
  end
67
-
68
- def logger
69
- nil
70
- end
71
70
  end
72
- class HoneybadgerVerificationController < ApplicationController; end
73
71
 
74
- Rails.application.routes_reloader.execute_if_updated
75
72
  Rails.application.routes.draw do
76
73
  match 'verify' => 'application#verify', :as => 'verify'
77
74
  end
@@ -48,9 +48,9 @@ module Honeybadger
48
48
 
49
49
  case response
50
50
  when Net::HTTPSuccess then
51
- log(:info, "Success: #{response.class}", response)
51
+ log(:info, "Success: #{response.class}", response, data)
52
52
  else
53
- log(:error, "Failure: #{response.class}", response)
53
+ log(:error, "Failure: #{response.class}", response, data)
54
54
  end
55
55
 
56
56
  if response && response.respond_to?(:body)
@@ -84,8 +84,14 @@ module Honeybadger
84
84
  URI.parse("#{protocol}://#{host}:#{port}").merge(NOTICES_URI)
85
85
  end
86
86
 
87
- def log(level, message, response = nil)
88
- logger.send(level, LOG_PREFIX + message) if logger
87
+ def log(level, message, response = nil, data = nil)
88
+ if logger
89
+ logger.send(level, LOG_PREFIX + message)
90
+
91
+ # Log the notice payload for debugging
92
+ logger.debug(LOG_PREFIX + "Notice: #{data}") if data
93
+ end
94
+
89
95
  Honeybadger.report_environment_info
90
96
  Honeybadger.report_response_body(response.body) if response && response.respond_to?(:body)
91
97
  end
@@ -1,7 +1,18 @@
1
1
  namespace :honeybadger do
2
2
  desc "Notify Honeybadger of a new deploy."
3
- task :deploy => :environment do
3
+ task :deploy do
4
4
  require 'honeybadger_tasks'
5
+
6
+ if defined?(Rails.root)
7
+ initializer_file = Rails.root.join('config', 'initializers','honeybadger.rb')
8
+
9
+ if initializer_file.exist?
10
+ load initializer_file
11
+ else
12
+ Rake::Task[:environment].invoke
13
+ end
14
+ end
15
+
5
16
  HoneybadgerTasks.deploy(:environment => ENV['TO'],
6
17
  :revision => ENV['REVISION'],
7
18
  :repository => ENV['REPO'],
@@ -4,7 +4,7 @@ require File.join(File.dirname(__FILE__), 'shared_tasks')
4
4
  namespace :honeybadger do
5
5
  desc "Verify your gem installation by sending a test exception to the honeybadger service"
6
6
  task :test => ['honeybadger:log_stdout', :environment] do
7
- RAILS_DEFAULT_LOGGER.level = Logger::DEBUG
7
+ RAILS_DEFAULT_LOGGER.level = Logger::INFO
8
8
 
9
9
  require 'action_controller/test_process'
10
10
 
@@ -17,6 +17,11 @@ namespace :honeybadger do
17
17
  exit
18
18
  end
19
19
 
20
+ if Honeybadger.configuration.async?
21
+ puts "Temporarily disabling asynchronous delivery"
22
+ Honeybadger.configuration.async = nil
23
+ end
24
+
20
25
  Honeybadger.configuration.development_environments = []
21
26
 
22
27
  catcher = Honeybadger::Rails::ActionControllerCatcher
data/test/test_helper.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  require 'test/unit'
2
2
 
3
- require 'bundler/setup'
4
-
5
3
  require 'mocha'
6
4
  require 'shoulda'
7
5
  require 'bourne'
@@ -27,14 +25,12 @@ end
27
25
  module DefinesConstants
28
26
  def setup
29
27
  @defined_constants = []
30
- Honeybadger.context.clear!
31
28
  end
32
29
 
33
30
  def teardown
34
31
  @defined_constants.each do |constant|
35
32
  Object.__send__(:remove_const, constant)
36
33
  end
37
- Honeybadger.context.clear!
38
34
  end
39
35
 
40
36
  def define_constant(name, value)
@@ -55,91 +51,95 @@ class CollectingSender
55
51
  end
56
52
  end
57
53
 
58
- module Honeybadger
59
- class UnitTest < Test::Unit::TestCase
60
- def assert_no_difference(expression, message = nil, &block)
61
- assert_difference expression, 0, message, &block
62
- end
54
+ class Test::Unit::TestCase
55
+ def teardown
56
+ Honeybadger.context.clear!
57
+ end
63
58
 
64
- def stub_sender
65
- stub('sender', :send_to_honeybadger => nil)
66
- end
59
+ def assert_no_difference(expression, message = nil, &block)
60
+ assert_difference expression, 0, message, &block
61
+ end
67
62
 
68
- def stub_sender!
69
- Honeybadger.sender = stub_sender
70
- end
63
+ def stub_sender
64
+ stub('sender', :send_to_honeybadger => nil)
65
+ end
66
+
67
+ def stub_sender!
68
+ Honeybadger.sender = stub_sender
69
+ end
71
70
 
72
- def stub_notice
73
- stub('notice', :to_json => 'some yaml', :ignore? => false)
71
+ def stub_notice
72
+ Honeybadger::Notice.new({}).tap do |notice|
73
+ notice.stubs(:ignored? => false, :to_json => '{"foo":"bar"}')
74
74
  end
75
+ end
75
76
 
76
- def stub_notice!
77
- stub_notice.tap do |notice|
78
- Honeybadger::Notice.stubs(:new => notice)
79
- end
77
+ def stub_notice!
78
+ stub_notice.tap do |notice|
79
+ Honeybadger::Notice.stubs(:new => notice)
80
80
  end
81
+ end
81
82
 
82
- def reset_config
83
- Honeybadger.configuration = nil
84
- Honeybadger.configure do |config|
85
- config.api_key = 'abc123'
86
- end
83
+ def reset_config
84
+ Honeybadger.configuration = nil
85
+ Honeybadger.configure do |config|
86
+ config.api_key = 'abc123'
87
87
  end
88
+ end
88
89
 
89
- def build_notice_data(exception = nil)
90
- exception ||= build_exception
91
- {
92
- :api_key => 'abc123',
93
- :error_class => exception.class.name,
94
- :error_message => "#{exception.class.name}: #{exception.message}",
95
- :backtrace => exception.backtrace,
96
- :environment => { 'PATH' => '/bin', 'REQUEST_URI' => '/users/1' },
97
- :request => {
98
- :params => { 'controller' => 'users', 'action' => 'show', 'id' => '1' },
99
- :rails_root => '/path/to/application',
100
- :url => "http://test.host/users/1"
101
- },
102
- :session => {
103
- :key => '123abc',
104
- :data => { 'user_id' => '5', 'flash' => { 'notice' => 'Logged in successfully' } }
105
- }
90
+ def build_notice_data(exception = nil)
91
+ exception ||= build_exception
92
+ {
93
+ :api_key => 'abc123',
94
+ :error_class => exception.class.name,
95
+ :error_message => "#{exception.class.name}: #{exception.message}",
96
+ :backtrace => exception.backtrace,
97
+ :environment => { 'PATH' => '/bin', 'REQUEST_URI' => '/users/1' },
98
+ :request => {
99
+ :params => { 'controller' => 'users', 'action' => 'show', 'id' => '1' },
100
+ :rails_root => '/path/to/application',
101
+ :url => "http://test.host/users/1"
102
+ },
103
+ :session => {
104
+ :key => '123abc',
105
+ :data => { 'user_id' => '5', 'flash' => { 'notice' => 'Logged in successfully' } }
106
106
  }
107
- end
107
+ }
108
+ end
108
109
 
109
- def build_exception(opts = {})
110
- backtrace = ["test/honeybadger/rack_test.rb:15:in `build_exception'",
111
- "test/honeybadger/rack_test.rb:52:in `test_delivers_exception_from_rack'",
112
- "/Users/josh/Developer/.rvm/gems/ruby-1.9.3-p0/gems/mocha-0.10.5/lib/mocha/integration/mini_test/version_230_to_262.rb:28:in `run'"]
113
- opts = { :backtrace => backtrace }.merge(opts)
114
- BacktracedException.new(opts)
115
- end
110
+ def build_exception(opts = {})
111
+ backtrace = ["test/honeybadger/rack_test.rb:15:in `build_exception'",
112
+ "test/honeybadger/rack_test.rb:52:in `test_delivers_exception_from_rack'",
113
+ "/Users/josh/Developer/.rvm/gems/ruby-1.9.3-p0/gems/mocha-0.10.5/lib/mocha/integration/mini_test/version_230_to_262.rb:28:in `run'"]
114
+ opts = { :backtrace => backtrace }.merge(opts)
115
+ BacktracedException.new(opts)
116
+ end
116
117
 
117
- def assert_array_starts_with(expected, actual)
118
- assert_respond_to actual, :to_ary
119
- array = actual.to_ary.reverse
120
- expected.reverse.each_with_index do |value, i|
121
- assert_equal value, array[i]
122
- end
118
+ def assert_array_starts_with(expected, actual)
119
+ assert_respond_to actual, :to_ary
120
+ array = actual.to_ary.reverse
121
+ expected.reverse.each_with_index do |value, i|
122
+ assert_equal value, array[i]
123
123
  end
124
+ end
124
125
 
125
- def assert_logged(expected)
126
- assert_received(Honeybadger, :write_verbose_log) do |expect|
127
- expect.with {|actual| actual =~ expected }
128
- end
126
+ def assert_logged(expected)
127
+ assert_received(Honeybadger, :write_verbose_log) do |expect|
128
+ expect.with {|actual| actual =~ expected }
129
129
  end
130
+ end
130
131
 
131
- def assert_not_logged(expected)
132
- assert_received(Honeybadger, :write_verbose_log) do |expect|
133
- expect.with {|actual| actual =~ expected }.never
134
- end
132
+ def assert_not_logged(expected)
133
+ assert_received(Honeybadger, :write_verbose_log) do |expect|
134
+ expect.with {|actual| actual =~ expected }.never
135
135
  end
136
+ end
136
137
 
137
- def assert_caught_and_sent
138
- assert !Honeybadger.sender.collected.empty?
139
- end
138
+ def assert_caught_and_sent
139
+ assert !Honeybadger.sender.collected.empty?
140
+ end
140
141
 
141
- def assert_caught_and_not_sent
142
- assert Honeybadger.sender.collected.empty?
143
- end
142
+ def assert_caught_and_not_sent
143
+ assert Honeybadger.sender.collected.empty?
144
144
  end
145
145
  end