honeybadger 1.14.0 → 1.15.0

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.
@@ -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