honeybadger 1.14.0 → 1.15.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,9 @@
1
+ ## Honeybadger 1.15.0 ##
2
+
3
+ * Send traces for slow requests.
4
+
5
+ *Benjamin Curtis*
6
+
1
7
  ## Honeybadger 1.14.0 ##
2
8
 
3
9
  * Catch exceptions in Thor tasks.
@@ -1,13 +1,12 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- honeybadger (1.14.0)
4
+ honeybadger (1.15.0)
5
5
  json
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- addressable (2.3.5)
11
10
  appraisal (0.5.1)
12
11
  bundler
13
12
  rake
@@ -25,8 +24,6 @@ GEM
25
24
  childprocess (0.3.6)
26
25
  ffi (~> 1.0, >= 1.0.6)
27
26
  coderay (1.1.0)
28
- crack (0.4.1)
29
- safe_yaml (~> 0.9.0)
30
27
  cucumber (1.3.10)
31
28
  builder (>= 2.1.2)
32
29
  diff-lcs (>= 1.1.3)
@@ -98,7 +95,6 @@ GEM
98
95
  rspec-instafail (0.2.4)
99
96
  rspec-mocks (2.14.3)
100
97
  ruby-progressbar (1.2.0)
101
- safe_yaml (0.9.7)
102
98
  sham_rack (1.3.4)
103
99
  rack
104
100
  sinatra (1.0)
@@ -107,9 +103,6 @@ GEM
107
103
  spoon (0.0.4)
108
104
  ffi
109
105
  thor (0.18.1)
110
- webmock (1.15.2)
111
- addressable (>= 2.2.7)
112
- crack (>= 0.3.2)
113
106
 
114
107
  PLATFORMS
115
108
  java
@@ -129,4 +122,3 @@ DEPENDENCIES
129
122
  rspec (~> 2.14.0)
130
123
  sham_rack (~> 1.3.0)
131
124
  sinatra
132
- webmock
@@ -4,8 +4,8 @@ Gem::Specification.new do |s|
4
4
  s.rubygems_version = '1.3.5'
5
5
 
6
6
  s.name = 'honeybadger'
7
- s.version = '1.14.0'
8
- s.date = '2014-05-28'
7
+ s.version = '1.15.0'
8
+ s.date = '2014-06-11'
9
9
 
10
10
  s.summary = 'Error reports you can be happy about.'
11
11
  s.description = 'Make managing application errors a more pleasant experience.'
@@ -33,7 +33,6 @@ Gem::Specification.new do |s|
33
33
  s.add_development_dependency('appraisal')
34
34
  s.add_development_dependency('fuubar')
35
35
  s.add_development_dependency('growl')
36
- s.add_development_dependency('webmock')
37
36
 
38
37
  ## Leave this section as-is. It will be automatically generated from the
39
38
  ## contents of your Git repository via the gemspec task. DO NOT REMOVE
@@ -100,11 +99,13 @@ Gem::Specification.new do |s|
100
99
  lib/honeybadger/integrations.rb
101
100
  lib/honeybadger/integrations/delayed_job.rb
102
101
  lib/honeybadger/integrations/delayed_job/plugin.rb
102
+ lib/honeybadger/integrations/net_http.rb
103
103
  lib/honeybadger/integrations/sidekiq.rb
104
104
  lib/honeybadger/integrations/thor.rb
105
105
  lib/honeybadger/monitor.rb
106
106
  lib/honeybadger/monitor/railtie.rb
107
107
  lib/honeybadger/monitor/sender.rb
108
+ lib/honeybadger/monitor/trace.rb
108
109
  lib/honeybadger/monitor/worker.rb
109
110
  lib/honeybadger/notice.rb
110
111
  lib/honeybadger/payload.rb
@@ -138,9 +139,11 @@ Gem::Specification.new do |s|
138
139
  spec/honeybadger/dependency_spec.rb
139
140
  spec/honeybadger/exception_extensions_spec.rb
140
141
  spec/honeybadger/integrations/delayed_job_spec.rb
142
+ spec/honeybadger/integrations/net_http_spec.rb
141
143
  spec/honeybadger/integrations/sidekiq_spec.rb
142
144
  spec/honeybadger/integrations/thor_spec.rb
143
145
  spec/honeybadger/logger_spec.rb
146
+ spec/honeybadger/monitor/trace_spec.rb
144
147
  spec/honeybadger/monitor/worker_spec.rb
145
148
  spec/honeybadger/notice_spec.rb
146
149
  spec/honeybadger/notifier_spec.rb
@@ -19,7 +19,7 @@ require 'honeybadger/exception_extensions'
19
19
  require 'honeybadger/railtie' if defined?(Rails::Railtie)
20
20
 
21
21
  module Honeybadger
22
- VERSION = '1.14.0'
22
+ VERSION = '1.15.0'
23
23
  LOG_PREFIX = "** [Honeybadger] "
24
24
 
25
25
  HEADERS = {
@@ -105,10 +105,15 @@ module Honeybadger
105
105
  configuration.features = features
106
106
 
107
107
  unless features['metrics']
108
- write_verbose_log("The optional metrics feature is not enabled for your account. Try restarting your app or contacting support@honeybadger.io if your subscription includes this feature.", :error)
108
+ write_verbose_log("The optional metrics feature is not enabled for your account. Try restarting your app or contacting support@honeybadger.io if your subscription includes this feature.", :warn)
109
109
  configuration.metrics = false
110
110
  end
111
111
 
112
+ unless features['traces']
113
+ write_verbose_log("The optional traces feature is not enabled for your account. Try restarting your app or contacting support@honeybadger.io if your subscription includes this feature.", :warn)
114
+ configuration.traces = false
115
+ end
116
+
112
117
  features
113
118
  end
114
119
  end
@@ -9,7 +9,7 @@ module Honeybadger
9
9
  :proxy_port, :proxy_user, :secure, :use_system_ssl_cert_chain, :framework,
10
10
  :user_information, :feedback, :rescue_rake_exceptions, :source_extract_radius,
11
11
  :send_request_session, :debug, :fingerprint, :hostname, :features, :metrics,
12
- :log_exception_on_send_failure, :send_local_variables].freeze
12
+ :log_exception_on_send_failure, :send_local_variables, :traces, :trace_threshold].freeze
13
13
 
14
14
  # The API key for your project, found on the project edit form.
15
15
  attr_accessor :api_key
@@ -61,6 +61,9 @@ module Honeybadger
61
61
  # A list of user agents that are being ignored. The array can be appended to.
62
62
  attr_reader :ignore_user_agent
63
63
 
64
+ # Traces must have a duration greater than this (in ms) to be recorded
65
+ attr_reader :trace_threshold
66
+
64
67
  # A list of environments in which notifications should not be sent.
65
68
  attr_accessor :development_environments
66
69
 
@@ -122,6 +125,9 @@ module Honeybadger
122
125
  # Send metrics?
123
126
  attr_accessor :metrics
124
127
 
128
+ # Send traces?
129
+ attr_accessor :traces
130
+
125
131
  # Which features the API says we have
126
132
  attr_accessor :features
127
133
 
@@ -185,8 +191,10 @@ module Honeybadger
185
191
  @hostname = Socket.gethostname
186
192
  @metrics = true
187
193
  @features = {'notices' => true, 'local_variables' => true}
194
+ @traces = true
188
195
  @limit = nil
189
196
  @feedback = true
197
+ @trace_threshold = 2000
190
198
  end
191
199
 
192
200
  # Public: Takes a block and adds it to the list of backtrace filters. When
@@ -242,6 +250,10 @@ module Honeybadger
242
250
  @ignore_user_agent = [names].flatten
243
251
  end
244
252
 
253
+ def trace_threshold=(threshold)
254
+ @trace_threshold = [threshold, 1000].max
255
+ end
256
+
245
257
  # Public: Allows config options to be read like a hash
246
258
  #
247
259
  # option - Key for a given attribute
@@ -282,6 +294,12 @@ module Honeybadger
282
294
  public? && @metrics
283
295
  end
284
296
 
297
+ # Public: Determines whether to send traces
298
+ #
299
+ def traces?
300
+ public? && @traces
301
+ end
302
+
285
303
  # Public: Configure async delivery
286
304
  #
287
305
  # block - An optional block containing an async handler
@@ -3,3 +3,4 @@ module Honeybadger::Integrations; end
3
3
  require 'honeybadger/integrations/delayed_job'
4
4
  require 'honeybadger/integrations/sidekiq'
5
5
  require 'honeybadger/integrations/thor'
6
+ require 'honeybadger/integrations/net_http'
@@ -5,7 +5,9 @@ module Honeybadger
5
5
  callbacks do |lifecycle|
6
6
  lifecycle.around(:invoke_job) do |job, &block|
7
7
  begin
8
- block.call(job)
8
+ Honeybadger::Monitor::Trace.instrument("#{job.payload_object.class}#perform", { :source => 'delayed_job', :jid => job.id, :class => job.payload_object.class.name }) do
9
+ block.call(job)
10
+ end
9
11
  rescue Exception => error
10
12
  ::Honeybadger.notify_or_ignore(
11
13
  :error_class => error.class.name,
@@ -0,0 +1,33 @@
1
+ module Honeybadger
2
+ module Integrations
3
+ module NetHttp
4
+ module Instrumentation
5
+ def self.included(base)
6
+ base.send(:alias_method, :request_without_honeybadger, :request)
7
+ base.send(:alias_method, :request, :request_with_honeybadger)
8
+ end
9
+
10
+ def request_with_honeybadger(*args, &block)
11
+ request = args[0]
12
+ uri = request.path.match(%r{https?://}) ? URI(request.path) : URI("http#{use_ssl? ? 's' : ''}://#{address}:#{port}#{request.path}")
13
+
14
+ if uri.host.match("honeybadger.io")
15
+ return request_without_honeybadger(*args, &block)
16
+ end
17
+
18
+ ActiveSupport::Notifications.instrument("net_http.request", { :uri => uri, :method => request.method }) do
19
+ request_without_honeybadger(*args, &block)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ Dependency.register do
27
+ requirement { defined?(::ActiveSupport::Notifications) }
28
+ requirement { defined?(::Net::HTTP) }
29
+ requirement { Honeybadger.configuration.traces? }
30
+
31
+ injection { ::Net::HTTP.send(:include, Integrations::NetHttp::Instrumentation) }
32
+ end
33
+ end
@@ -4,7 +4,9 @@ module Honeybadger
4
4
  class Middleware
5
5
  def call(worker, msg, queue)
6
6
  Honeybadger.context.clear!
7
- yield
7
+ Honeybadger::Monitor::Trace.instrument("#{msg['class']}#perform", { :source => 'sidekiq', :jid => msg['jid'], :class => msg['class'] }) do
8
+ yield
9
+ end
8
10
  end
9
11
  end
10
12
  end
@@ -1,6 +1,7 @@
1
1
  require "honeybadger/array"
2
2
  require "honeybadger/monitor/sender"
3
3
  require "honeybadger/monitor/worker"
4
+ require "honeybadger/monitor/trace"
4
5
  require "honeybadger/monitor/railtie" if defined?(Rails)
5
6
 
6
7
  module Honeybadger
@@ -3,8 +3,35 @@ module Honeybadger
3
3
  class Railtie < ::Rails::Railtie
4
4
 
5
5
  config.after_initialize do
6
- if Honeybadger.configuration.metrics?
6
+ if Honeybadger.configuration.traces?
7
+ ActiveSupport::Notifications.subscribe('start_processing.action_controller') do |name, started, finished, id, data|
8
+ Trace.create(id)
9
+ end
10
+
11
+ ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
12
+ event = ActiveSupport::Notifications::Event.new(*args)
13
+ Monitor.worker.trace.add(event) if Monitor.worker.trace and event.name != 'SCHEMA'
14
+ end
15
+
16
+ ActiveSupport::Notifications.subscribe(/^render_(template|action|collection)\.action_view/) do |*args|
17
+ event = ActiveSupport::Notifications::Event.new(*args)
18
+ Monitor.worker.trace.add(event) if Monitor.worker.trace
19
+ end
20
+
21
+ ActiveSupport::Notifications.subscribe('net_http.request') do |*args|
22
+ event = ActiveSupport::Notifications::Event.new(*args)
23
+ Monitor.worker.trace.add(event) if Monitor.worker.trace
24
+ end
7
25
 
26
+ ActiveSupport::Notifications.subscribe('process_action.action_controller') do |*args|
27
+ event = ActiveSupport::Notifications::Event.new(*args)
28
+ if event.payload[:controller] && event.payload[:action] && Monitor.worker.trace
29
+ Monitor.worker.trace.complete(event)
30
+ end
31
+ end
32
+ end
33
+
34
+ if Honeybadger.configuration.metrics?
8
35
  ActiveSupport::Notifications.subscribe('process_action.action_controller') do |*args|
9
36
  event = ActiveSupport::Notifications::Event.new(*args)
10
37
  status = event.payload[:exception] ? 500 : event.payload[:status]
@@ -18,7 +45,6 @@ module Honeybadger
18
45
  Monitor.worker.timing("app.controller.#{controller}.#{action}.db", event.payload[:db_runtime]) if event.payload[:db_runtime]
19
46
  end
20
47
  end
21
-
22
48
  end
23
49
  end
24
50
 
@@ -10,6 +10,7 @@ module Honeybadger
10
10
  end
11
11
 
12
12
  if Net::HTTPSuccess === response
13
+ log(:info, "Metrics Success: #{response.class}", response, data) if Honeybadger.configuration.debug
13
14
  true
14
15
  else
15
16
  Honeybadger.configuration.features['metrics'] = false if Net::HTTPForbidden === response
@@ -21,6 +22,27 @@ module Honeybadger
21
22
  true
22
23
  end
23
24
 
25
+ def send_traces(data)
26
+ return unless Honeybadger.configuration.traces?
27
+ return unless Honeybadger.configuration.features['traces']
28
+
29
+ response = rescue_http_errors do
30
+ http_connection.post('/v1/traces', data.to_json, http_headers)
31
+ end
32
+
33
+ if Net::HTTPSuccess === response
34
+ log(:info, "Traces Success: #{response.class}", response, data) if Honeybadger.configuration.debug
35
+ true
36
+ else
37
+ Honeybadger.configuration.features['traces'] = false if Net::HTTPForbidden === response
38
+ log(:error, "Traces Failure: #{response.class}", response, data)
39
+ false
40
+ end
41
+ rescue => e
42
+ log(:error, "[Honeybadger::Monitor::Sender#send_traces] Error: #{e.class} - #{e.message}\nBacktrace:\n#{e.backtrace.join("\n\t")}")
43
+ true
44
+ end
45
+
24
46
  end
25
47
  end
26
48
  end
@@ -0,0 +1,170 @@
1
+ require 'securerandom'
2
+
3
+ module Honeybadger
4
+ module Monitor
5
+ class Trace
6
+ UUIDS_ENABLED = SecureRandom.respond_to?(:uuid).freeze
7
+
8
+ attr_reader :id, :duration, :key
9
+
10
+ def self.create(id)
11
+ Thread.current[:hb_trace_id] = id
12
+ Monitor.worker.pending_traces[id] = new(id)
13
+ end
14
+
15
+ def self.instrument(key, payload = {}, &block)
16
+ create(generate_secure_id).instrument(key, payload, &block)
17
+ end
18
+
19
+ def self.generate_secure_id
20
+ UUIDS_ENABLED ? SecureRandom.uuid : SecureRandom.hex
21
+ end
22
+
23
+ def initialize(id)
24
+ @id = id
25
+ @events = []
26
+ @meta = {}
27
+ @duration = 0
28
+ end
29
+
30
+ def add(event)
31
+ ce = clean_event(event)
32
+ @events << ce.to_a if ce.render?
33
+ end
34
+
35
+ def complete(event)
36
+ @meta = clean_event(event).to_h
37
+ @duration = event.duration
38
+ @key = "#{event.payload[:controller]}##{event.payload[:action]}"
39
+ Monitor.worker.queue_trace
40
+ end
41
+
42
+ def instrument(key, payload)
43
+ @key = key
44
+ @meta = payload
45
+ started = Time.now
46
+ yield
47
+ rescue Exception => e
48
+ @meta[:exception] = [e.class.name, e.message]
49
+ raise e
50
+ ensure
51
+ @meta.merge!(:duration => @duration = 1000.0 * (Time.now - started))
52
+ Monitor.worker.queue_trace
53
+ end
54
+
55
+ def to_h
56
+ @meta.merge({ :events => @events, :key => @key })
57
+ end
58
+
59
+ protected
60
+
61
+ def clean_event(event)
62
+ TraceCleaner.create(event)
63
+ end
64
+
65
+ end
66
+
67
+ module TraceCleaner
68
+
69
+ def self.create(event)
70
+ Classes[event.name].new(event)
71
+ end
72
+
73
+ class Base
74
+ attr_reader :event
75
+
76
+ def initialize(event)
77
+ @event = event
78
+ end
79
+
80
+ def render?
81
+ true
82
+ end
83
+
84
+ def payload
85
+ event.payload
86
+ end
87
+
88
+ def to_s
89
+ payload[:path] || payload[:key] || payload.inspect
90
+ end
91
+
92
+ def to_h
93
+ { :name => event.name, :desc => to_s, :duration => event.duration }
94
+ end
95
+
96
+ def to_a
97
+ [ event.name, event.duration, to_s ]
98
+ end
99
+
100
+ end
101
+
102
+ class NetHttpRequest < Base
103
+ Replacement = "..."
104
+ def to_s
105
+ uri = payload[:uri]
106
+ uri.user = Replacement if uri.user
107
+ uri.password = Replacement if uri.password
108
+ uri.query = Replacement if uri.query
109
+ "#{payload[:method]} #{uri}"
110
+ end
111
+ end
112
+
113
+ class ActiveRecord < Base
114
+ Schema = "SCHEMA".freeze
115
+ SchemaMigrations = /schema_migrations/.freeze
116
+ EscapedQuotes = /(\\"|\\')/.freeze
117
+ SQuotedData = /'(?:[^']|'')*'/.freeze
118
+ DQuotedData = /"(?:[^"]|"")*"/.freeze
119
+ NumericData = /\b\d+\b/.freeze
120
+ Newline = /\n/.freeze
121
+ Replacement = "?".freeze
122
+ EmptyReplacement = "".freeze
123
+ DoubleQuoters = /(postgres|sqlite|postgis)/.freeze
124
+
125
+ def render?
126
+ event.payload[:name] != Schema && !event.payload[:sql].match(SchemaMigrations)
127
+ end
128
+
129
+ def to_s
130
+ return "Super long query" if event.payload[:sql].length > 1024
131
+ sql = event.payload[:sql]
132
+ sql = sql.gsub(EscapedQuotes, EmptyReplacement).gsub(SQuotedData, Replacement)
133
+ sql = sql.gsub(DQuotedData, Replacement) unless ::ActiveRecord::Base.connection_config[:adapter] =~ DoubleQuoters
134
+ sql.gsub(NumericData, Replacement).gsub(Newline, EmptyReplacement).squeeze(' ')
135
+ end
136
+ end
137
+
138
+ class ActionView < Base
139
+ EmptyReplacement = "".freeze
140
+
141
+ def to_s
142
+ event.payload[:identifier].to_s.gsub(::Rails.root.to_s + '/', EmptyReplacement)
143
+ end
144
+ end
145
+
146
+ class ActionController < Base
147
+ def payload
148
+ event.payload.reject {|k, v| k == :params }
149
+ end
150
+
151
+ def to_s
152
+ payload.inspect
153
+ end
154
+
155
+ def to_h
156
+ payload.merge({ :duration => event.duration })
157
+ end
158
+ end
159
+
160
+ Classes = Hash.new(Base).merge({
161
+ 'sql.active_record' => ActiveRecord,
162
+ 'render_template.action_view' => ActionView,
163
+ 'render_partial.action_view' => ActionView,
164
+ 'render_collection.action_view' => ActionView,
165
+ 'process_action.action_controller' => ActionController,
166
+ 'net_http.request' => NetHttpRequest
167
+ })
168
+ end
169
+ end
170
+ end
@@ -11,7 +11,8 @@ module Honeybadger
11
11
 
12
12
  def initialize
13
13
  init_metrics
14
- @delay = 60
14
+ init_traces
15
+ @delay = defined?(::Rails) && ::Rails.env.development? ? 10 : 60
15
16
  @per_request = 100
16
17
  @sender = Monitor::Sender.new(Honeybadger.configuration)
17
18
  @lock = Mutex.new
@@ -21,9 +22,15 @@ module Honeybadger
21
22
 
22
23
  def start
23
24
  @thread = MetricsThread.new do
24
- until Thread.current[:should_exit] do
25
- send_metrics
26
- sleep @delay
25
+ begin
26
+ until Thread.current[:should_exit] do
27
+ send_metrics
28
+ send_traces
29
+ sleep @delay
30
+ end
31
+ rescue Exception => e
32
+ Honeybadger.write_verbose_log("Error in MetricsThread (shutting down): #{e.class} - #{e.message}\n#{e.backtrace.join("\n\t")}", :error)
33
+ raise e
27
34
  end
28
35
  end
29
36
  end
@@ -40,12 +47,34 @@ module Honeybadger
40
47
  add_metric(name, value, :counter)
41
48
  end
42
49
 
50
+ def pending_traces
51
+ @pending_traces ||= {}
52
+ end
53
+
54
+ def trace
55
+ Thread.current[:hb_trace_id] ? @pending_traces[Thread.current[:hb_trace_id]] : nil
56
+ end
57
+
58
+ def queue_trace
59
+ @lock.synchronize do
60
+ if trace.duration > Honeybadger.configuration.trace_threshold && (!@traces[trace.key] || @traces[trace.key].duration < trace.duration)
61
+ @traces[trace.key] = trace
62
+ end
63
+ @pending_traces[Thread.current[:hb_trace_id]] = nil
64
+ Thread.current[:hb_trace_id] = nil
65
+ end
66
+ end
67
+
43
68
  protected
44
69
 
45
70
  def init_metrics
46
71
  @metrics = { :timing => {}, :counter => {} }
47
72
  end
48
73
 
74
+ def init_traces
75
+ @traces = {}
76
+ end
77
+
49
78
  def collect_metrics
50
79
  @lock.synchronize do
51
80
  metrics = @metrics
@@ -54,6 +83,14 @@ module Honeybadger
54
83
  end
55
84
  end
56
85
 
86
+ def collect_traces
87
+ @lock.synchronize do
88
+ traces = @traces.values
89
+ init_traces
90
+ traces
91
+ end
92
+ end
93
+
57
94
  def send_metrics
58
95
  metrics = collect_metrics
59
96
  return unless metrics[:timing].any? || metrics[:counter].any?
@@ -79,6 +116,16 @@ module Honeybadger
79
116
  end
80
117
  end
81
118
 
119
+ def send_traces
120
+ collect_traces.each_slice(@per_request) do |t|
121
+ begin
122
+ @sender.send_traces({ :traces => t.compact.map(&:to_h), :environment => Honeybadger.configuration.environment_name, :hostname => Honeybadger.configuration.hostname })
123
+ rescue Exception => e
124
+ log(:error, "[Honeybadger::Monitor::Worker#send_traces] Failed to send #{t.count} metrics: #{e.class} - #{e.message}\nBacktrace:\n#{e.backtrace.join("\n\t")}")
125
+ end
126
+ end
127
+ end
128
+
82
129
  def add_metric(name, value, kind)
83
130
  @lock.synchronize do
84
131
  (@metrics[kind][name] ||= Honeybadger::Array.new) << value
@@ -1,5 +1,4 @@
1
1
  require 'honeybadger'
2
- require 'honeybadger/monitor'
3
2
  require 'rails'
4
3
 
5
4
  module Honeybadger
@@ -44,8 +43,12 @@ module Honeybadger
44
43
  ::ActionDispatch::ShowExceptions.send(:include,Honeybadger::Rails::Middleware::ExceptionsCatcher)
45
44
  end
46
45
 
47
- Honeybadger::Dependency.inject!
48
46
  Honeybadger.ping(Honeybadger.configuration)
47
+
48
+ # Inject last, in case we're depending on configuration from ping.
49
+ Honeybadger::Dependency.inject!
49
50
  end
50
51
  end
51
52
  end
53
+
54
+ require 'honeybadger/monitor'
@@ -252,6 +252,24 @@ describe Honeybadger::Configuration do
252
252
  expect(Honeybadger.configuration).to be_a Honeybadger::Configuration
253
253
  end
254
254
 
255
+ describe '#trace_threshold=' do
256
+ let(:config) { Honeybadger::Configuration.new }
257
+
258
+ subject { config.trace_threshold = value; config.trace_threshold }
259
+
260
+ context "value is above 1000" do
261
+ let(:value) { 2000 }
262
+
263
+ it { should eq 2000 }
264
+ end
265
+
266
+ context "value is below 1000" do
267
+ let(:value) { 100 }
268
+
269
+ it { should eq 1000 }
270
+ end
271
+ end
272
+
255
273
  def assert_config_default(option, default_value, config = nil)
256
274
  config ||= Honeybadger::Configuration.new
257
275
  expect(config.send(option)).to eq default_value
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ begin
4
+ require 'active_support/notifications'
5
+ rescue LoadError
6
+ nil
7
+ end
8
+
9
+ describe "Net::HTTP Dependency" do
10
+ before do
11
+ Honeybadger::Dependency.reset!
12
+ end
13
+
14
+ if defined?(ActiveSupport::Notifications)
15
+ context "when active support notifications are installed" do
16
+ it "installs instrumentation" do
17
+ Honeybadger::Dependency.inject!
18
+ expect(Net::HTTP.instance_method(:request)).to eq Net::HTTP.instance_method(:request_with_honeybadger)
19
+ end
20
+ end
21
+ else
22
+ context "when active support notifications are not installed" do
23
+ it "does not install instrumentation" do
24
+ Honeybadger::Dependency.inject!
25
+ expect(Net::HTTP.instance_methods).not_to include(:request_with_honeybadger)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -55,20 +55,22 @@ describe Honeybadger do
55
55
  context "429 error response" do
56
56
  let(:failure_class) do
57
57
  if RUBY_VERSION !~ /^1/
58
- 'Net::HTTPTooManyRequests'
58
+ Net::HTTPTooManyRequests
59
59
  else
60
- 'Net::HTTPClientError'
60
+ Net::HTTPClientError
61
61
  end
62
62
  end
63
63
 
64
+ let(:http) { stub_http(:response => failure_class.new('1.2', '429', 'Peace out'), :body => '{"error":"something went wrong"}') }
65
+
64
66
  before do
65
67
  reset_config
66
68
  stub_verbose_log
67
- stub_request(:post, /api\.honeybadger\.io\/v1\/notices/).to_return(:status => 429, :body => '{"error":"something went wrong"}')
68
69
  end
69
70
 
70
71
  it "logs the response" do
71
- Honeybadger.should_receive(:write_verbose_log).with(/Failure: #{failure_class}/, :error)
72
+ http.should_receive(:post).with(Honeybadger::Sender::NOTICES_URI, kind_of(String), kind_of(Hash))
73
+ Honeybadger.should_receive(:write_verbose_log).with(/Failure: #{failure_class.name}/, :error)
72
74
  Honeybadger.should_receive(:write_verbose_log).with(/Environment Info:/)
73
75
  Honeybadger.should_receive(:write_verbose_log).with(/something went wrong/)
74
76
  Honeybadger.notify(RuntimeError.new('oops!'))
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+ require 'honeybadger/monitor'
3
+
4
+ describe Honeybadger::Monitor::Trace do
5
+ describe "::instrument" do
6
+ it "creates a new trace" do
7
+ Honeybadger::Monitor::Trace.should_receive(:new).and_call_original
8
+ described_class.instrument('testing', {}){}
9
+ end
10
+ end
11
+ end
@@ -8,8 +8,9 @@ describe Honeybadger::Monitor::Worker do
8
8
  before(:each) do
9
9
  Thread.stub(:new)
10
10
 
11
- # Create an attr_reader for @metrics and @sender
11
+ # Create attr_readers for testing values
12
12
  instance.stub(:metrics) { instance.instance_variable_get(:@metrics) }
13
+ instance.stub(:traces) { instance.instance_variable_get(:@traces) }
13
14
  instance.stub(:sender) { instance.instance_variable_get(:@sender) }
14
15
  end
15
16
 
@@ -29,6 +30,12 @@ describe Honeybadger::Monitor::Worker do
29
30
  end
30
31
  end
31
32
 
33
+ describe '@traces' do
34
+ subject { instance.traces }
35
+
36
+ it { should be_empty }
37
+ end
38
+
32
39
  describe '@delay' do
33
40
  subject { instance.instance_variable_get(:@delay) }
34
41
  it { should eq 60 }
@@ -48,6 +48,7 @@ describe 'Honeybadger' do
48
48
 
49
49
  context "result is truthy" do
50
50
  before { sender.should_receive(:ping).and_return(result) }
51
+ before { Honeybadger.stub(:write_verbose_log) }
51
52
 
52
53
  context "result does not contain features" do
53
54
  let(:result) { {} }
@@ -66,7 +67,17 @@ describe 'Honeybadger' do
66
67
  specify { expect { invoke_subject }.to change(config, :metrics).to(false) }
67
68
 
68
69
  it "logs that metrics are disabled" do
69
- Honeybadger.should_receive(:write_verbose_log).with(/metrics feature is not enabled/, :error)
70
+ Honeybadger.should_receive(:write_verbose_log).with(/metrics feature is not enabled/, :warn)
71
+ invoke_subject
72
+ end
73
+ end
74
+
75
+ context "traces are disabled by service" do
76
+ let(:result) { {'features' => {'traces' => false}} }
77
+ specify { expect { invoke_subject }.to change(config, :traces).to(false) }
78
+
79
+ it "logs that traces are disabled" do
80
+ Honeybadger.should_receive(:write_verbose_log).with(/traces feature is not enabled/, :warn)
70
81
  invoke_subject
71
82
  end
72
83
  end
@@ -2,14 +2,11 @@ require 'spec_helper'
2
2
 
3
3
  describe Honeybadger::Sender do
4
4
  before { reset_config }
5
-
6
- before do
7
- stub_request(:post, /api\.honeybadger\.io\/v1\/notices/).to_return(:body => '{"id":"123"}')
8
- end
5
+ let(:http) { stub_http }
9
6
 
10
7
  it "makes a single request when sending notices" do
8
+ http.should_receive(:post).with(Honeybadger::Sender::NOTICES_URI, kind_of(String), kind_of(Hash))
11
9
  Honeybadger.notify(RuntimeError.new('oops!'))
12
- assert_requested :post, 'https://api.honeybadger.io/v1/notices/', :times => 1
13
10
  end
14
11
 
15
12
  it "posts to Honeybadger when using an HTTP proxy" do
@@ -49,16 +46,16 @@ describe Honeybadger::Sender do
49
46
  context 'notice is a hash' do
50
47
  it 'uses api_key from hash when present' do
51
48
  sender = build_sender(:api_key => 'asdf')
49
+ http.should_receive(:post).with(Honeybadger::Sender::NOTICES_URI, kind_of(String), hash_including('X-API-Key' => 'zxcv'))
52
50
  send_exception(:sender => sender, :notice => { 'api_key' => 'zxcv' })
53
- assert_requested :post, 'https://api.honeybadger.io/v1/notices/', :times => 1, :headers => { 'x-api-key' => 'zxcv' }
54
51
  end
55
52
  end
56
53
 
57
54
  context 'notice is a Honeybadger::Notice' do
58
55
  it 'uses api_key from notice when present' do
59
56
  sender = build_sender(:api_key => 'asdf')
57
+ http.should_receive(:post).with(Honeybadger::Sender::NOTICES_URI, kind_of(String), hash_including('X-API-Key' => 'zxcv'))
60
58
  send_exception(:sender => sender, :notice => Honeybadger::Notice.new(:api_key => 'zxcv'))
61
- assert_requested :post, 'https://api.honeybadger.io/v1/notices/', :times => 1, :headers => { 'x-api-key' => 'zxcv' }
62
59
  end
63
60
  end
64
61
  end
@@ -1,6 +1,5 @@
1
1
  require 'rspec'
2
2
  require 'honeybadger'
3
- require 'webmock/rspec'
4
3
 
5
4
  # Require files in spec/support/ and its subdirectories.
6
5
  Dir[File.expand_path('../../spec/support/**/*.rb', __FILE__)].each {|f| require f}
metadata CHANGED
@@ -1,32 +1,36 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: honeybadger
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.14.0
4
+ version: 1.15.0
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Joshua Wood
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2014-05-28 00:00:00.000000000 Z
12
+ date: 2014-06-11 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: json
15
16
  requirement: !ruby/object:Gem::Requirement
17
+ none: false
16
18
  requirements:
17
- - - '>='
19
+ - - ! '>='
18
20
  - !ruby/object:Gem::Version
19
21
  version: '0'
20
22
  type: :runtime
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
23
26
  requirements:
24
- - - '>='
27
+ - - ! '>='
25
28
  - !ruby/object:Gem::Version
26
29
  version: '0'
27
30
  - !ruby/object:Gem::Dependency
28
31
  name: cucumber
29
32
  requirement: !ruby/object:Gem::Requirement
33
+ none: false
30
34
  requirements:
31
35
  - - ~>
32
36
  - !ruby/object:Gem::Version
@@ -34,6 +38,7 @@ dependencies:
34
38
  type: :development
35
39
  prerelease: false
36
40
  version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
37
42
  requirements:
38
43
  - - ~>
39
44
  - !ruby/object:Gem::Version
@@ -41,6 +46,7 @@ dependencies:
41
46
  - !ruby/object:Gem::Dependency
42
47
  name: rspec
43
48
  requirement: !ruby/object:Gem::Requirement
49
+ none: false
44
50
  requirements:
45
51
  - - ~>
46
52
  - !ruby/object:Gem::Version
@@ -48,6 +54,7 @@ dependencies:
48
54
  type: :development
49
55
  prerelease: false
50
56
  version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
51
58
  requirements:
52
59
  - - ~>
53
60
  - !ruby/object:Gem::Version
@@ -55,6 +62,7 @@ dependencies:
55
62
  - !ruby/object:Gem::Dependency
56
63
  name: sham_rack
57
64
  requirement: !ruby/object:Gem::Requirement
65
+ none: false
58
66
  requirements:
59
67
  - - ~>
60
68
  - !ruby/object:Gem::Version
@@ -62,6 +70,7 @@ dependencies:
62
70
  type: :development
63
71
  prerelease: false
64
72
  version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
65
74
  requirements:
66
75
  - - ~>
67
76
  - !ruby/object:Gem::Version
@@ -69,6 +78,7 @@ dependencies:
69
78
  - !ruby/object:Gem::Dependency
70
79
  name: capistrano
71
80
  requirement: !ruby/object:Gem::Requirement
81
+ none: false
72
82
  requirements:
73
83
  - - ~>
74
84
  - !ruby/object:Gem::Version
@@ -76,6 +86,7 @@ dependencies:
76
86
  type: :development
77
87
  prerelease: false
78
88
  version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
79
90
  requirements:
80
91
  - - ~>
81
92
  - !ruby/object:Gem::Version
@@ -83,6 +94,7 @@ dependencies:
83
94
  - !ruby/object:Gem::Dependency
84
95
  name: guard
85
96
  requirement: !ruby/object:Gem::Requirement
97
+ none: false
86
98
  requirements:
87
99
  - - ~>
88
100
  - !ruby/object:Gem::Version
@@ -90,6 +102,7 @@ dependencies:
90
102
  type: :development
91
103
  prerelease: false
92
104
  version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
93
106
  requirements:
94
107
  - - ~>
95
108
  - !ruby/object:Gem::Version
@@ -97,113 +110,113 @@ dependencies:
97
110
  - !ruby/object:Gem::Dependency
98
111
  name: guard-rspec
99
112
  requirement: !ruby/object:Gem::Requirement
113
+ none: false
100
114
  requirements:
101
- - - '>='
115
+ - - ! '>='
102
116
  - !ruby/object:Gem::Version
103
117
  version: '0'
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
107
122
  requirements:
108
- - - '>='
123
+ - - ! '>='
109
124
  - !ruby/object:Gem::Version
110
125
  version: '0'
111
126
  - !ruby/object:Gem::Dependency
112
127
  name: rake
113
128
  requirement: !ruby/object:Gem::Requirement
129
+ none: false
114
130
  requirements:
115
- - - '>='
131
+ - - ! '>='
116
132
  - !ruby/object:Gem::Version
117
133
  version: '0'
118
134
  type: :development
119
135
  prerelease: false
120
136
  version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
121
138
  requirements:
122
- - - '>='
139
+ - - ! '>='
123
140
  - !ruby/object:Gem::Version
124
141
  version: '0'
125
142
  - !ruby/object:Gem::Dependency
126
143
  name: sinatra
127
144
  requirement: !ruby/object:Gem::Requirement
145
+ none: false
128
146
  requirements:
129
- - - '>='
147
+ - - ! '>='
130
148
  - !ruby/object:Gem::Version
131
149
  version: '0'
132
150
  type: :development
133
151
  prerelease: false
134
152
  version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
135
154
  requirements:
136
- - - '>='
155
+ - - ! '>='
137
156
  - !ruby/object:Gem::Version
138
157
  version: '0'
139
158
  - !ruby/object:Gem::Dependency
140
159
  name: aruba
141
160
  requirement: !ruby/object:Gem::Requirement
161
+ none: false
142
162
  requirements:
143
- - - '>='
163
+ - - ! '>='
144
164
  - !ruby/object:Gem::Version
145
165
  version: '0'
146
166
  type: :development
147
167
  prerelease: false
148
168
  version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
149
170
  requirements:
150
- - - '>='
171
+ - - ! '>='
151
172
  - !ruby/object:Gem::Version
152
173
  version: '0'
153
174
  - !ruby/object:Gem::Dependency
154
175
  name: appraisal
155
176
  requirement: !ruby/object:Gem::Requirement
177
+ none: false
156
178
  requirements:
157
- - - '>='
179
+ - - ! '>='
158
180
  - !ruby/object:Gem::Version
159
181
  version: '0'
160
182
  type: :development
161
183
  prerelease: false
162
184
  version_requirements: !ruby/object:Gem::Requirement
185
+ none: false
163
186
  requirements:
164
- - - '>='
187
+ - - ! '>='
165
188
  - !ruby/object:Gem::Version
166
189
  version: '0'
167
190
  - !ruby/object:Gem::Dependency
168
191
  name: fuubar
169
192
  requirement: !ruby/object:Gem::Requirement
193
+ none: false
170
194
  requirements:
171
- - - '>='
195
+ - - ! '>='
172
196
  - !ruby/object:Gem::Version
173
197
  version: '0'
174
198
  type: :development
175
199
  prerelease: false
176
200
  version_requirements: !ruby/object:Gem::Requirement
201
+ none: false
177
202
  requirements:
178
- - - '>='
203
+ - - ! '>='
179
204
  - !ruby/object:Gem::Version
180
205
  version: '0'
181
206
  - !ruby/object:Gem::Dependency
182
207
  name: growl
183
208
  requirement: !ruby/object:Gem::Requirement
209
+ none: false
184
210
  requirements:
185
- - - '>='
211
+ - - ! '>='
186
212
  - !ruby/object:Gem::Version
187
213
  version: '0'
188
214
  type: :development
189
215
  prerelease: false
190
216
  version_requirements: !ruby/object:Gem::Requirement
217
+ none: false
191
218
  requirements:
192
- - - '>='
193
- - !ruby/object:Gem::Version
194
- version: '0'
195
- - !ruby/object:Gem::Dependency
196
- name: webmock
197
- requirement: !ruby/object:Gem::Requirement
198
- requirements:
199
- - - '>='
200
- - !ruby/object:Gem::Version
201
- version: '0'
202
- type: :development
203
- prerelease: false
204
- version_requirements: !ruby/object:Gem::Requirement
205
- requirements:
206
- - - '>='
219
+ - - ! '>='
207
220
  - !ruby/object:Gem::Version
208
221
  version: '0'
209
222
  description: Make managing application errors a more pleasant experience.
@@ -274,11 +287,13 @@ files:
274
287
  - lib/honeybadger/integrations.rb
275
288
  - lib/honeybadger/integrations/delayed_job.rb
276
289
  - lib/honeybadger/integrations/delayed_job/plugin.rb
290
+ - lib/honeybadger/integrations/net_http.rb
277
291
  - lib/honeybadger/integrations/sidekiq.rb
278
292
  - lib/honeybadger/integrations/thor.rb
279
293
  - lib/honeybadger/monitor.rb
280
294
  - lib/honeybadger/monitor/railtie.rb
281
295
  - lib/honeybadger/monitor/sender.rb
296
+ - lib/honeybadger/monitor/trace.rb
282
297
  - lib/honeybadger/monitor/worker.rb
283
298
  - lib/honeybadger/notice.rb
284
299
  - lib/honeybadger/payload.rb
@@ -312,9 +327,11 @@ files:
312
327
  - spec/honeybadger/dependency_spec.rb
313
328
  - spec/honeybadger/exception_extensions_spec.rb
314
329
  - spec/honeybadger/integrations/delayed_job_spec.rb
330
+ - spec/honeybadger/integrations/net_http_spec.rb
315
331
  - spec/honeybadger/integrations/sidekiq_spec.rb
316
332
  - spec/honeybadger/integrations/thor_spec.rb
317
333
  - spec/honeybadger/logger_spec.rb
334
+ - spec/honeybadger/monitor/trace_spec.rb
318
335
  - spec/honeybadger/monitor/worker_spec.rb
319
336
  - spec/honeybadger/notice_spec.rb
320
337
  - spec/honeybadger/notifier_spec.rb
@@ -335,7 +352,6 @@ files:
335
352
  - spec/support/helpers.rb
336
353
  homepage: http://www.honeybadger.io
337
354
  licenses: []
338
- metadata: {}
339
355
  post_install_message:
340
356
  rdoc_options:
341
357
  - --charset=UTF-8
@@ -343,18 +359,23 @@ rdoc_options:
343
359
  require_paths:
344
360
  - lib
345
361
  required_ruby_version: !ruby/object:Gem::Requirement
362
+ none: false
346
363
  requirements:
347
- - - '>='
364
+ - - ! '>='
348
365
  - !ruby/object:Gem::Version
349
366
  version: '0'
367
+ segments:
368
+ - 0
369
+ hash: 4388463689066388096
350
370
  required_rubygems_version: !ruby/object:Gem::Requirement
371
+ none: false
351
372
  requirements:
352
- - - '>='
373
+ - - ! '>='
353
374
  - !ruby/object:Gem::Version
354
375
  version: '0'
355
376
  requirements: []
356
377
  rubyforge_project:
357
- rubygems_version: 2.1.5
378
+ rubygems_version: 1.8.23
358
379
  signing_key:
359
380
  specification_version: 2
360
381
  summary: Error reports you can be happy about.
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA1:
3
- metadata.gz: 75f654d44aa6fb5226d1a12be042c7265bd3d26b
4
- data.tar.gz: 8bdea660c9780b0bab2369852d40e4f2ad50238f
5
- SHA512:
6
- metadata.gz: b6d6e31805c4ced6597e3f37fac9525163709fc4dbf1c247a8723940261b0154160dbe9e0afcda23e97cca07e657bea76ac1635e0a61a06b3b6e69f3593298b2
7
- data.tar.gz: ebd6fdc8f7ee8245f859ee288751c8289a34aa40cbcbd9b5e286cf7d909748788a5970235d5f6b7db72b6e48467037d5af98390e4ddc031e41edd03461a8045b