honeybadger 2.0.12 → 2.1.0.beta.1

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.
@@ -8,31 +8,26 @@ module Honeybadger
8
8
  def initialize(env = ENV)
9
9
  env.each_pair do |k,v|
10
10
  next unless k.match(CONFIG_KEY)
11
- next if DISALLOWED_KEYS.include?(CONFIG_MAPPING[$1])
12
- self[CONFIG_MAPPING[$1] || $1.downcase.to_sym] = cast_value(v)
11
+ next unless config_key = CONFIG_MAPPING[$1]
12
+ self[config_key] = cast_value(v, OPTIONS[config_key][:type])
13
13
  end
14
14
  end
15
15
 
16
16
  private
17
17
 
18
- def cast_value(value)
19
- if value.match(ARRAY_VALUES)
20
- return value.split(ARRAY_VALUES).map(&method(:cast_value))
21
- end
18
+ def cast_value(value, type = String)
19
+ v = value.to_s
22
20
 
23
- case value
24
- when /\Atrue\Z/
25
- true
26
- when /\Afalse\Z/
27
- false
28
- when /\Anil\Z/
29
- nil
30
- when /\A\d+\z/
31
- value.to_i
32
- when /\A\d+\.\d+\z/
33
- value.to_f
21
+ if type == Boolean
22
+ !!(v =~ /\A(true|t|1)\z/i)
23
+ elsif type == Array
24
+ v.split(ARRAY_VALUES).map(&method(:cast_value))
25
+ elsif type == Integer
26
+ v.to_i
27
+ elsif type == Float
28
+ v.to_f
34
29
  else
35
- value.to_s
30
+ v
36
31
  end
37
32
  end
38
33
  end
@@ -5,6 +5,8 @@ require 'erb'
5
5
  module Honeybadger
6
6
  class Config
7
7
  class Yaml < ::Hash
8
+ DISALLOWED_KEYS = [:'config.path'].freeze
9
+
8
10
  def initialize(path, env = 'production')
9
11
  @path = path.kind_of?(Pathname) ? path : Pathname.new(path)
10
12
 
@@ -1,5 +1,7 @@
1
1
  require 'rails'
2
2
  require 'yaml'
3
+ require 'honeybadger/util/sanitizer'
4
+ require 'honeybadger/util/request_payload'
3
5
 
4
6
  module Honeybadger
5
7
  module Init
@@ -30,7 +32,7 @@ module Honeybadger
30
32
  Trace.current.add_query(event) if Trace.current and event.name != 'SCHEMA'
31
33
  end
32
34
 
33
- ActiveSupport::Notifications.subscribe(/^render_(template|action|collection)\.action_view/) do |*args|
35
+ ActiveSupport::Notifications.subscribe(/^render_(template|partial|action|collection)\.action_view/) do |*args|
34
36
  event = ActiveSupport::Notifications::Event.new(*args)
35
37
  Trace.current.add(event) if Trace.current
36
38
  end
@@ -43,7 +45,10 @@ module Honeybadger
43
45
  ActiveSupport::Notifications.subscribe('process_action.action_controller') do |*args|
44
46
  event = ActiveSupport::Notifications::Event.new(*args)
45
47
  if Trace.current && event.payload[:controller] && event.payload[:action]
46
- Trace.current.complete(event)
48
+ payload = { source: 'web' }
49
+ payload[:path] = Util::Sanitizer.new(filters: config.params_filters).filter_url(event.payload[:path]) if event.payload[:path]
50
+ payload[:request] = request_data(event, config)
51
+ Trace.current.complete(event, payload)
47
52
  end
48
53
  end
49
54
  end
@@ -53,14 +58,6 @@ module Honeybadger
53
58
  event = ActiveSupport::Notifications::Event.new(*args)
54
59
  status = event.payload[:exception] ? 500 : event.payload[:status]
55
60
  Agent.timing("app.request.#{status}", event.duration)
56
-
57
- controller = event.payload[:controller]
58
- action = event.payload[:action]
59
- if controller && action
60
- Agent.timing("app.controller.#{controller}.#{action}.total", event.duration)
61
- Agent.timing("app.controller.#{controller}.#{action}.view", event.payload[:view_runtime]) if event.payload[:view_runtime]
62
- Agent.timing("app.controller.#{controller}.#{action}.db", event.payload[:db_runtime]) if event.payload[:db_runtime]
63
- end
64
61
  end
65
62
  end
66
63
  end
@@ -77,6 +74,27 @@ module Honeybadger
77
74
  :framework => :rails
78
75
  }
79
76
  end
77
+
78
+ def request_data(event, config)
79
+ h = {
80
+ url: event.payload[:path],
81
+ component: event.payload[:controller],
82
+ action: event.payload[:action],
83
+ params: event.payload[:params]
84
+ }
85
+ h.merge!(config.request_hash)
86
+ h.delete_if {|k,v| config.excluded_request_keys.include?(k) }
87
+ h[:sanitizer] = Util::Sanitizer.new(filters: config.params_filters)
88
+ Util::RequestPayload.build(h).update({
89
+ context: context_data
90
+ })
91
+ end
92
+
93
+ def context_data
94
+ if Thread.current[:__honeybadger_context]
95
+ Util::Sanitizer.new.sanitize(Thread.current[:__honeybadger_context])
96
+ end
97
+ end
80
98
  end
81
99
  end
82
100
  end
@@ -4,12 +4,7 @@ module Honeybadger
4
4
  ::Sinatra::Base.class_eval do
5
5
  class << self
6
6
  def build_with_honeybadger(*args, &block)
7
- config = Honeybadger::Config.new(honeybadger_config(self))
8
- if Honeybadger.start(config)
9
- use(Honeybadger::Rack::ErrorNotifier, config) if config.feature?(:notices) && config[:'exceptions.enabled']
10
- use(Honeybadger::Rack::MetricsReporter, config) if config.feature?(:metrics) && config[:'metrics.enabled']
11
- end
12
-
7
+ install_honeybadger
13
8
  build_without_honeybadger(*args, &block)
14
9
  end
15
10
  alias :build_without_honeybadger :build
@@ -20,6 +15,21 @@ module Honeybadger
20
15
  api_key: defined?(honeybadger_api_key) ? honeybadger_api_key : nil
21
16
  }
22
17
  end
18
+
19
+ def install_honeybadger
20
+ config = Honeybadger::Config.new(honeybadger_config(self))
21
+
22
+ return unless config[:'sinatra.enabled']
23
+ return unless Honeybadger.start(config)
24
+
25
+ install_honeybadger_middleware(Honeybadger::Rack::ErrorNotifier, config) if config.feature?(:notices) && config[:'exceptions.enabled']
26
+ install_honeybadger_middleware(Honeybadger::Rack::MetricsReporter, config) if config.feature?(:metrics) && config[:'metrics.enabled']
27
+ end
28
+
29
+ def install_honeybadger_middleware(klass, config)
30
+ return if middleware.any? {|m| m[0] == klass }
31
+ use(klass, config)
32
+ end
23
33
  end
24
34
  end
25
35
  end
@@ -1,13 +1,12 @@
1
1
  require 'json'
2
2
  require 'securerandom'
3
3
  require 'forwardable'
4
- require 'ostruct'
5
4
 
6
5
  require 'honeybadger/version'
7
6
  require 'honeybadger/backtrace'
8
7
  require 'honeybadger/util/stats'
9
8
  require 'honeybadger/util/sanitizer'
10
- require 'honeybadger/rack/request_hash'
9
+ require 'honeybadger/util/request_payload'
11
10
 
12
11
  module Honeybadger
13
12
  NOTIFIER = {
@@ -32,16 +31,6 @@ module Honeybadger
32
31
  # Internal: Matches lines beginning with ./
33
32
  RELATIVE_ROOT = Regexp.new('^\.\/').freeze
34
33
 
35
- # Internal: default values to use for request data.
36
- REQUEST_DEFAULTS = {
37
- url: nil,
38
- component: nil,
39
- action: nil,
40
- params: {}.freeze,
41
- session: {}.freeze,
42
- cgi_data: {}.freeze
43
- }.freeze
44
-
45
34
  MAX_EXCEPTION_CAUSES = 5
46
35
 
47
36
  class Notice
@@ -79,24 +68,25 @@ module Honeybadger
79
68
  attr_reader :source
80
69
 
81
70
  # Public: CGI variables such as HTTP_METHOD.
82
- def_delegator :@request, :cgi_data
71
+ def cgi_data; @request[:cgi_data]; end
83
72
 
84
73
  # Public: A hash of parameters from the query string or post body.
85
- def_delegator :@request, :params
74
+ def params; @request[:params]; end
86
75
  alias_method :parameters, :params
87
76
 
88
77
  # Public: The component (if any) which was used in this request. (usually the controller)
89
- def_delegator :@request, :component
78
+ def component; @request[:component]; end
90
79
  alias_method :controller, :component
91
80
 
92
81
  # Public: The action (if any) that was called in this request.
93
- def_delegator :@request, :action
82
+ def action; @request[:action]; end
94
83
 
95
84
  # Public: A hash of session data from the request.
96
85
  def_delegator :@request, :session
86
+ def session; @request[:session]; end
97
87
 
98
88
  # Public: The URL at which the error occurred (if any).
99
- def_delegator :@request, :url
89
+ def url; @request[:url]; end
100
90
 
101
91
  # Public: Local variables are extracted from first frame of backtrace.
102
92
  attr_reader :local_variables
@@ -138,24 +128,21 @@ module Honeybadger
138
128
  @config = config
139
129
 
140
130
  @sanitizer = Util::Sanitizer.new
141
- @filtered_sanitizer = Util::Sanitizer.new(filters: config.params_filters)
142
131
 
143
- @exception = opts[:exception]
132
+ @exception = unwrap_exception(opts[:exception])
144
133
  @error_class = exception_attribute(:error_class) {|exception| exception.class.name }
145
- @error_message = trim_size(1024) do
146
- exception_attribute(:error_message, 'Notification') do |exception|
147
- "#{exception.class.name}: #{exception.message}"
148
- end
134
+ @error_message = exception_attribute(:error_message, 'Notification') do |exception|
135
+ "#{exception.class.name}: #{exception.message}"
149
136
  end
150
137
  @backtrace = parse_backtrace(exception_attribute(:backtrace, caller))
151
138
  @source = extract_source_from_backtrace(@backtrace, config, opts)
152
139
  @fingerprint = construct_fingerprint(opts)
153
140
 
154
- @request = OpenStruct.new(construct_request_hash(config.request, opts, config.excluded_request_keys))
141
+ @request = construct_request_hash(config, opts)
155
142
 
156
143
  @context = construct_context_hash(opts)
157
144
 
158
- @causes = unwrap_causes(opts[:exception])
145
+ @causes = unwrap_causes(@exception)
159
146
 
160
147
  @tags = construct_tags(opts[:tags])
161
148
  @tags = construct_tags(context[:tags]) | @tags if context
@@ -186,16 +173,10 @@ module Honeybadger
186
173
  tags: s(tags),
187
174
  causes: s(causes)
188
175
  },
189
- request: {
190
- url: sanitized_url,
191
- component: s(component),
192
- action: s(action),
193
- params: f(params),
194
- session: f(session),
195
- cgi_data: f(cgi_data),
176
+ request: @request.update({
196
177
  context: s(context),
197
178
  local_variables: s(local_variables)
198
- },
179
+ }),
199
180
  server: {
200
181
  project_root: s(config[:root]),
201
182
  environment_name: s(config[:env]),
@@ -239,7 +220,7 @@ module Honeybadger
239
220
 
240
221
  private
241
222
 
242
- attr_reader :config, :opts, :context, :stats, :api_key, :now, :pid, :causes, :sanitizer, :filtered_sanitizer
223
+ attr_reader :config, :opts, :context, :stats, :api_key, :now, :pid, :causes, :sanitizer
243
224
 
244
225
  def ignore_by_origin?
245
226
  opts[:origin] == :rake && !config[:'exceptions.rescue_rake']
@@ -262,7 +243,7 @@ module Honeybadger
262
243
  #
263
244
  # Returns attribute value from args or exception, otherwise default
264
245
  def exception_attribute(attribute, default = nil, &block)
265
- opts[attribute] || (opts[:exception] && from_exception(attribute, &block)) || default
246
+ opts[attribute] || (exception && from_exception(attribute, &block)) || default
266
247
  end
267
248
 
268
249
  # Gets a property named +attribute+ from an exception.
@@ -274,12 +255,12 @@ module Honeybadger
274
255
  # If no block is given, a method with the same name as +attribute+ will be
275
256
  # invoked for the value.
276
257
  def from_exception(attribute)
277
- return unless opts[:exception]
258
+ return unless exception
278
259
 
279
260
  if block_given?
280
- yield(opts[:exception])
261
+ yield(exception)
281
262
  else
282
- opts[:exception].send(attribute)
263
+ exception.send(attribute)
283
264
  end
284
265
  end
285
266
 
@@ -302,50 +283,24 @@ module Honeybadger
302
283
  ignored_class ? @ignore_by_class.call(ignored_class) : config[:'exceptions.ignore'].any?(&@ignore_by_class)
303
284
  end
304
285
 
305
- # Limit size of string to bytes
306
- #
307
- # input - The String to be trimmed.
308
- # bytes - The Integer bytes to trim.
309
- # block - An optional block used in place of input.
310
- #
311
- # Examples
312
- #
313
- # trimmed = trim_size("Honeybadger doesn't care", 3)
314
- #
315
- # trimmed = trim_size(3) do
316
- # "Honeybadger doesn't care"
317
- # end
318
- #
319
- # Returns trimmed String
320
- def trim_size(*args, &block)
321
- input, bytes = args.first, args.last
322
- input = yield if block_given?
323
- input = input.dup
324
- input = input[0...bytes] if input.respond_to?(:size) && input.size > bytes
325
- input
326
- end
327
-
328
286
  def construct_backtrace_filters(opts)
329
287
  [
330
288
  opts[:callbacks] ? opts[:callbacks].backtrace_filter : nil
331
289
  ].compact | BACKTRACE_FILTERS
332
290
  end
333
291
 
334
- def construct_request_hash(rack_request, opts, excluded_keys = [])
292
+ # Internal: Construct the request object with data from various sources.
293
+ #
294
+ # Returns Request.
295
+ def construct_request_hash(config, opts)
335
296
  request = {}
336
- request.merge!(Rack::RequestHash.new(rack_request)) if rack_request
337
-
297
+ request.merge!(config.request_hash)
298
+ request.merge!(opts)
338
299
  request[:component] = opts[:controller] if opts.has_key?(:controller)
339
300
  request[:params] = opts[:parameters] if opts.has_key?(:parameters)
340
-
341
- REQUEST_DEFAULTS.each do |key, default|
342
- request[key] = opts[key] if opts.has_key?(key)
343
- request[key] = default if !request[key] || excluded_keys.include?(key)
344
- end
345
-
346
- request[:session] = request[:session][:data] if request[:session][:data]
347
-
348
- request
301
+ request.delete_if {|k,v| config.excluded_request_keys.include?(k) }
302
+ request[:sanitizer] = Util::Sanitizer.new(filters: config.params_filters)
303
+ Util::RequestPayload.build(request)
349
304
  end
350
305
 
351
306
  def construct_context_hash(opts)
@@ -356,21 +311,19 @@ module Honeybadger
356
311
  end
357
312
 
358
313
  def extract_source_from_backtrace(backtrace, config, opts)
359
- if backtrace.lines.empty?
360
- nil
314
+ return nil if backtrace.lines.empty?
315
+
316
+ # ActionView::Template::Error has its own source_extract method.
317
+ # If present, use that instead.
318
+ if exception.respond_to?(:source_extract) && exception.source_extract
319
+ Hash[exception.source_extract.split("\n").map do |line|
320
+ parts = line.split(': ')
321
+ [parts[0].strip, parts[1] || '']
322
+ end]
323
+ elsif backtrace.application_lines.any?
324
+ backtrace.application_lines.first.source(config[:'exceptions.source_radius'])
361
325
  else
362
- # ActionView::Template::Error has its own source_extract method.
363
- # If present, use that instead.
364
- if opts[:exception].respond_to?(:source_extract) && opts[:exception].source_extract
365
- Hash[exception.source_extract.split("\n").map do |line|
366
- parts = line.split(': ')
367
- [parts[0].strip, parts[1] || '']
368
- end]
369
- elsif backtrace.application_lines.any?
370
- backtrace.application_lines.first.source(config[:'exceptions.source_radius'])
371
- else
372
- backtrace.lines.first.source(config[:'exceptions.source_radius'])
373
- end
326
+ backtrace.lines.first.source(config[:'exceptions.source_radius'])
374
327
  end
375
328
  end
376
329
 
@@ -408,15 +361,6 @@ module Honeybadger
408
361
  sanitizer.sanitize(data)
409
362
  end
410
363
 
411
- def f(data)
412
- filtered_sanitizer.sanitize(data)
413
- end
414
-
415
- def sanitized_url
416
- return nil unless url
417
- filtered_sanitizer.filter_url(s(url))
418
- end
419
-
420
364
  # Internal: Fetch local variables from first frame of backtrace.
421
365
  #
422
366
  # exception - The Exception containing the bindings stack.
@@ -458,6 +402,17 @@ module Honeybadger
458
402
  )
459
403
  end
460
404
 
405
+ # Internal: Unwrap the exception so that original exception is ignored or
406
+ # reported.
407
+ #
408
+ # exception - The exception which was rescued.
409
+ #
410
+ # Returns the Exception to report.
411
+ def unwrap_exception(exception)
412
+ return exception unless config[:'exceptions.unwrap']
413
+ exception_cause(exception) || exception
414
+ end
415
+
461
416
  # Internal: Fetch cause from exception.
462
417
  #
463
418
  # exception - Exception to fetch cause from.
@@ -484,7 +439,7 @@ module Honeybadger
484
439
  while (e = exception_cause(e)) && i < MAX_EXCEPTION_CAUSES
485
440
  c << {
486
441
  class: e.class.name,
487
- message: trim_size(1024) { e.message },
442
+ message: e.message,
488
443
  backtrace: parse_backtrace(e.backtrace || caller)
489
444
  }
490
445
  i += 1
@@ -8,21 +8,34 @@ module Honeybadger
8
8
  callbacks do |lifecycle|
9
9
  lifecycle.around(:invoke_job) do |job, &block|
10
10
  begin
11
+ begin #buildin suport for Delayed::PerformableMethod
12
+ component = job.payload_object.object.is_a?(Class) ? job.payload_object.object.name : job.payload_object.object.class.name
13
+ action = job.payload_object.method_name.to_s
14
+ rescue #fallback to support all other classes
15
+ component = job.payload_object.class.name
16
+ action = 'perform'
17
+ end
18
+
19
+ ::Honeybadger.context(
20
+ :component => component,
21
+ :action => action,
22
+ :job_id => job.id,
23
+ :handler => job.handler,
24
+ :last_error => job.last_error,
25
+ :attempts => job.attempts,
26
+ :queue => job.queue
27
+ )
28
+
11
29
  ::Honeybadger::Trace.instrument("#{job.payload_object.class}#perform", {source: 'delayed_job', jid: job.id, class: job.payload_object.class.name}) do
12
30
  block.call(job)
13
31
  end
14
32
  rescue Exception => error
15
33
  ::Honeybadger.notify_or_ignore(
34
+ :component => component,
35
+ :action => action,
16
36
  :error_class => error.class.name,
17
37
  :error_message => "#{ error.class.name }: #{ error.message }",
18
- :backtrace => error.backtrace,
19
- :context => {
20
- :job_id => job.id,
21
- :handler => job.handler,
22
- :last_error => job.last_error,
23
- :attempts => job.attempts,
24
- :queue => job.queue
25
- }
38
+ :backtrace => error.backtrace
26
39
  ) if job.attempts.to_i >= ::Honeybadger::Agent.config[:'delayed_job.attempt_threshold'].to_i
27
40
  raise error
28
41
  ensure