honeybadger 2.0.12 → 2.1.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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