bugsnag 2.5.1 → 2.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 492b7ad630873a696cfe885ba27c95d652e8bcd2
4
- data.tar.gz: aabd629b69e8dbfaf6bd528e1e22e42f56cfdc1a
3
+ metadata.gz: ef55d682c90467747f2be118b83bd3a85b156b1d
4
+ data.tar.gz: 62d482b4c71148dfe0713ea33df2289bb1f5c1fb
5
5
  SHA512:
6
- metadata.gz: 91b142a6f7107413fc6b10a9764013d5c76181ca1e19fc655bad998a0d5ef26e30d5f6eae7ee11e1cc6c917a7dccf754e59e46afcb00565c6e246064014ada86
7
- data.tar.gz: 015f3dba4008ae2cdafdb8bc89ff103bf5072d3688f8ee98d9ce40f1e7f1d4b2f98837b20c91ce98775f784a2487c3eb814cd7f90b59b962186ab45a3e06d42a
6
+ metadata.gz: 4a6af3d51710c9f736aaa59c99031f5e5a21e2b7da41182183cb4f06f3ae38fd61ce6993c460f304e35cc9398c7447b8f415c1e00da86a2fc08d0f0a42b28b7a
7
+ data.tar.gz: 9c15c561756b5e4feda9c6b98910010f59850dad2788d5bd9ae7ba92d3181200855b54b4e60a9b6f53a0e80c075ee6fb2932b68ac5e71e5c68f70ff4d50ee595
data/CHANGELOG.md CHANGED
@@ -1,9 +1,17 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ 2.6.0
5
+ -----
6
+ - Collect and send snippets of source code to Bugsnag
7
+ - Fix resque integration
8
+ - Allow configuration of delivery method (from the default `:thread_queue` to `:synchronous`)
9
+ - Fix parameter filtering in rails 2
10
+ - Allow pathname in project root
11
+
4
12
  2.5.1
5
13
  -----
6
- - Send headers through
14
+ - Collect and send HTTP headers to bugsnag to help debugging
7
15
 
8
16
  2.5.0
9
17
  -----
data/README.md CHANGED
@@ -552,6 +552,16 @@ send your rack environment, set the `send_environment` option to `true`.
552
552
  config.send_environment = true
553
553
  ```
554
554
 
555
+ ###send_code
556
+
557
+ Bugsnag automatically sends a small snippet of the code that crashed to help you diagnose
558
+ even faster from within your dashboard. If you don't want to send this snippet you can
559
+ set the `send_code` option to `false`.
560
+
561
+ ```ruby
562
+ config.send_code = false
563
+ ```
564
+
555
565
  Bugsnag Middleware
556
566
  ------------------
557
567
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.5.1
1
+ 2.6.0
data/bugsnag.gemspec CHANGED
@@ -31,5 +31,6 @@ Gem::Specification.new do |s|
31
31
  s.add_development_dependency 'rspec'
32
32
  s.add_development_dependency 'rdoc'
33
33
  s.add_development_dependency 'pry'
34
+ s.add_development_dependency 'webmock'
34
35
  end
35
36
 
data/lib/bugsnag.rb CHANGED
@@ -1,13 +1,16 @@
1
1
  require "rubygems"
2
2
 
3
3
  require "bugsnag/version"
4
- require "bugsnag/queue"
5
4
  require "bugsnag/configuration"
6
5
  require "bugsnag/meta_data"
7
6
  require "bugsnag/notification"
8
7
  require "bugsnag/helpers"
9
8
  require "bugsnag/deploy"
10
9
 
10
+ require "bugsnag/delivery"
11
+ require "bugsnag/delivery/synchronous"
12
+ require "bugsnag/delivery/thread_queue"
13
+
11
14
  require "bugsnag/rack"
12
15
  require "bugsnag/railtie" if defined?(Rails::Railtie)
13
16
 
@@ -11,6 +11,7 @@ module Bugsnag
11
11
  attr_accessor :auto_notify
12
12
  attr_accessor :use_ssl
13
13
  attr_accessor :send_environment
14
+ attr_accessor :send_code
14
15
  attr_accessor :project_root
15
16
  attr_accessor :app_version
16
17
  attr_accessor :app_type
@@ -27,6 +28,7 @@ module Bugsnag
27
28
  attr_accessor :proxy_password
28
29
  attr_accessor :timeout
29
30
  attr_accessor :hostname
31
+ attr_accessor :delivery_method
30
32
 
31
33
  attr_writer :ignore_classes
32
34
 
@@ -50,16 +52,20 @@ module Bugsnag
50
52
 
51
53
  DEFAULT_IGNORE_USER_AGENTS = [].freeze
52
54
 
55
+ DEFAULT_DELIVERY_METHOD = :thread_queue
56
+
53
57
  def initialize
54
58
  # Set up the defaults
55
59
  self.auto_notify = true
56
60
  self.use_ssl = true
57
61
  self.send_environment = false
62
+ self.send_code = true
58
63
  self.params_filters = Set.new(DEFAULT_PARAMS_FILTERS)
59
64
  self.ignore_classes = Set.new(DEFAULT_IGNORE_CLASSES)
60
65
  self.ignore_user_agents = Set.new(DEFAULT_IGNORE_USER_AGENTS)
61
66
  self.endpoint = DEFAULT_ENDPOINT
62
67
  self.hostname = default_hostname
68
+ self.delivery_method = DEFAULT_DELIVERY_METHOD
63
69
 
64
70
  # Read the API key from the environment
65
71
  self.api_key = ENV["BUGSNAG_API_KEY"]
@@ -0,0 +1,18 @@
1
+ module Bugsnag
2
+ module Delivery
3
+ class << self
4
+ def register(name, delivery_method)
5
+ delivery_methods[name.to_sym] = delivery_method
6
+ end
7
+
8
+ def [](name)
9
+ delivery_methods[name.to_sym]
10
+ end
11
+
12
+ private
13
+ def delivery_methods
14
+ @delivery_methods ||= {}
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,25 @@
1
+ module Bugsnag
2
+ module Delivery
3
+ class Synchronous
4
+ HEADERS = {"Content-Type" => "application/json"}
5
+ TIMEOUT = 5
6
+
7
+ class << self
8
+ def deliver(url, body)
9
+ begin
10
+ response = HTTParty.post(url, {:body => body, :headers => HEADERS, :timeout => TIMEOUT})
11
+ Bugsnag.debug("Notification to #{url} finished, response was #{response.code}, payload was #{body}")
12
+ rescue StandardError => e
13
+ # KLUDGE: Since we don't re-raise http exceptions, this breaks rspec
14
+ raise if e.class.to_s == "RSpec::Expectations::ExpectationNotMetError"
15
+
16
+ Bugsnag.warn("Notification to #{url} failed, #{e.inspect}")
17
+ Bugsnag.warn(e.backtrace)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ Bugsnag::Delivery.register(:synchronous, Bugsnag::Delivery::Synchronous)
@@ -0,0 +1,51 @@
1
+ require "thread"
2
+
3
+ module Bugsnag
4
+ module Delivery
5
+ class ThreadQueue < Synchronous
6
+ MAX_OUTSTANDING_REQUESTS = 100
7
+ STOP = Object.new
8
+
9
+ class << self
10
+ def deliver(url, body)
11
+ if queue.length > MAX_OUTSTANDING_REQUESTS
12
+ Bugsnag.warn("Dropping notification, #{queue.length} outstanding requests")
13
+ return
14
+ end
15
+
16
+ # Add delivery to the worker thread
17
+ queue.push proc { super }
18
+
19
+ # Make sure the worker thread is started
20
+ ensure_worker_thread_started
21
+ end
22
+
23
+ private
24
+ def queue
25
+ @queue ||= Queue.new
26
+ end
27
+
28
+ def ensure_worker_thread_started
29
+ unless @worker_thread
30
+ @worker_thread = Thread.new do
31
+ while x = queue.pop
32
+ break if x == STOP
33
+ x.call
34
+ end
35
+ end
36
+
37
+ at_exit do
38
+ Bugsnag.warn("Waiting for #{queue.length} outstanding request(s)") unless queue.empty?
39
+ queue.push STOP
40
+ @worker_thread.join
41
+ end
42
+ end
43
+
44
+ @worker_thread
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ Bugsnag::Delivery.register(:thread_queue, Bugsnag::Delivery::ThreadQueue)
@@ -24,51 +24,23 @@ module Bugsnag
24
24
 
25
25
  CURRENT_PAYLOAD_VERSION = "2"
26
26
 
27
- # HTTParty settings
28
- headers "Content-Type" => "application/json"
29
- default_timeout 5
30
-
31
27
  attr_accessor :context
32
28
  attr_accessor :user
33
29
  attr_accessor :configuration
34
30
  attr_accessor :meta_data
35
31
 
36
- @queue = Bugsnag::Queue.new
37
-
38
32
  class << self
39
- def deliver_exception_payload(endpoint, payload)
40
- begin
41
- payload_string = Bugsnag::Helpers.dump_json(payload)
42
-
43
- # If the payload is going to be too long, we trim the hashes to send
44
- # a minimal payload instead
45
- if payload_string.length > 128000
46
- payload[:events].each {|e| e[:metaData] = Bugsnag::Helpers.reduce_hash_size(e[:metaData])}
47
- payload_string = Bugsnag::Helpers.dump_json(payload)
48
- end
49
-
50
- do_post(endpoint, payload_string)
33
+ def deliver_exception_payload(url, payload, configuration=Bugsnag.configuration, delivery_method=nil)
51
34
 
52
- rescue StandardError => e
53
- # KLUDGE: Since we don't re-raise http exceptions, this breaks rspec
54
- raise if e.class.to_s == "RSpec::Expectations::ExpectationNotMetError"
55
-
56
- Bugsnag.warn("Notification to #{endpoint} failed, #{e.inspect}")
57
- Bugsnag.warn(e.backtrace)
35
+ # If the payload is going to be too long, we trim the hashes to send
36
+ # a minimal payload instead
37
+ payload_string = Bugsnag::Helpers.dump_json(payload)
38
+ if payload_string.length > 128000
39
+ payload[:events].each {|e| e[:metaData] = Bugsnag::Helpers.reduce_hash_size(e[:metaData])}
40
+ payload_string = Bugsnag::Helpers.dump_json(payload)
58
41
  end
59
42
 
60
- end
61
-
62
- def do_post(endpoint, payload_string)
63
- @queue.push proc{
64
- begin
65
- response = post(endpoint, {:body => payload_string})
66
- Bugsnag.debug("Notification to #{endpoint} finished, response was #{response.code}, payload was #{payload_string}")
67
- rescue StandardError => e
68
- Bugsnag.warn("Notification to #{endpoint} failed, #{e.inspect}")
69
- Bugsnag.warn(e.backtrace)
70
- end
71
- }
43
+ Bugsnag::Delivery[delivery_method || configuration.delivery_method].deliver(url, payload_string)
72
44
  end
73
45
  end
74
46
 
@@ -93,6 +65,11 @@ module Bugsnag
93
65
  @overrides.delete :api_key
94
66
  end
95
67
 
68
+ if @overrides.key? :delivery_method
69
+ @delivery_method = @overrides[:delivery_method]
70
+ @overrides.delete :delivery_method
71
+ end
72
+
96
73
  # Unwrap exceptions
97
74
  @exceptions = []
98
75
 
@@ -272,7 +249,8 @@ module Bugsnag
272
249
  :events => [payload_event]
273
250
  }
274
251
 
275
- self.class.deliver_exception_payload(endpoint, payload)
252
+ # Deliver the payload
253
+ self.class.deliver_exception_payload(endpoint, payload, @configuration, @delivery_method)
276
254
  end
277
255
  end
278
256
 
@@ -391,6 +369,10 @@ module Bugsnag
391
369
  trace_hash[:inProject] = true if @configuration.project_root && file.match(/^#{@configuration.project_root}/) && !file.match(/vendor\//)
392
370
  trace_hash[:lineNumber] = line_str.to_i
393
371
 
372
+ if @configuration.send_code
373
+ trace_hash[:code] = code(file, trace_hash[:lineNumber])
374
+ end
375
+
394
376
  # Clean up the file path in the stacktrace
395
377
  if defined?(Bugsnag.configuration.project_root) && Bugsnag.configuration.project_root.to_s != ''
396
378
  file.sub!(/#{Bugsnag.configuration.project_root}\//, "")
@@ -413,5 +395,41 @@ module Bugsnag
413
395
  end
414
396
  end.compact
415
397
  end
398
+
399
+ def code(file, line_number, num_lines = 7)
400
+ code_hash = {}
401
+
402
+ from_line = [line_number - num_lines, 1].max
403
+
404
+ # Populate code hash with line numbers and code lines
405
+ File.open(file) do |f|
406
+ current_line_number = 0
407
+ f.each_line do |line|
408
+ current_line_number += 1
409
+
410
+ next if current_line_number < from_line
411
+
412
+ code_hash[current_line_number] = line[0...200].rstrip
413
+
414
+ break if code_hash.length >= ( num_lines * 1.5 ).ceil
415
+ end
416
+ end
417
+
418
+ while code_hash.length > num_lines
419
+ last_line = code_hash.keys.max
420
+ first_line = code_hash.keys.min
421
+
422
+ if (last_line - line_number) > (line_number - first_line)
423
+ code_hash.delete(last_line)
424
+ else
425
+ code_hash.delete(first_line)
426
+ end
427
+ end
428
+
429
+ code_hash
430
+ rescue
431
+ Bugsnag.warn("Error fetching code: #{$!.inspect}")
432
+ nil
433
+ end
416
434
  end
417
435
  end
data/lib/bugsnag/rack.rb CHANGED
@@ -14,7 +14,7 @@ module Bugsnag
14
14
  config.release_stage ||= ENV["RACK_ENV"] if ENV["RACK_ENV"]
15
15
 
16
16
  # Try to set the project_root if it hasn't already been set, or show a warning if we can't
17
- unless config.project_root && !config.project_root.empty?
17
+ unless config.project_root && !config.project_root.to_s.empty?
18
18
  if defined?(settings)
19
19
  config.project_root = settings.root
20
20
  else
@@ -53,4 +53,4 @@ module Bugsnag
53
53
  Bugsnag.clear_request_data
54
54
  end
55
55
  end
56
- end
56
+ end
data/lib/bugsnag/rails.rb CHANGED
@@ -19,26 +19,42 @@ module Bugsnag
19
19
  ActiveRecord::Base.send(:include, Bugsnag::Rails::ActiveRecordRescue)
20
20
  end
21
21
 
22
- # Try to find where to log to
23
- rails_logger = nil
24
- if defined?(::Rails.logger)
25
- rails_logger = ::Rails.logger
26
- elsif defined?(RAILS_DEFAULT_LOGGER)
27
- rails_logger = RAILS_DEFAULT_LOGGER
28
- end
29
-
30
22
  Bugsnag.configure do |config|
31
23
  config.logger ||= rails_logger
32
- config.release_stage = RAILS_ENV if defined?(RAILS_ENV)
33
- config.project_root = RAILS_ROOT if defined?(RAILS_ROOT)
24
+ config.release_stage = rails_env if rails_env
25
+ config.project_root = rails_root if rails_root
34
26
 
35
27
  config.middleware.insert_before(Bugsnag::Middleware::Callbacks,Bugsnag::Middleware::Rails2Request)
36
28
  end
37
29
 
38
30
  # Auto-load configuration settings from config/bugsnag.yml if it exists
39
- config_file = File.join(RAILS_ROOT, "config", "bugsnag.yml")
31
+ config_file = File.join(rails_root, "config", "bugsnag.yml")
40
32
  config = YAML.load_file(config_file) if File.exists?(config_file)
41
- Bugsnag.configure(config[RAILS_ENV] ? config[RAILS_ENV] : config) if config
33
+ Bugsnag.configure(config[rails_env] ? config[rails_env] : config) if config
34
+ end
35
+
36
+ def self.rails_logger
37
+ if defined?(::Rails.logger)
38
+ rails_logger = ::Rails.logger
39
+ elsif defined?(RAILS_DEFAULT_LOGGER)
40
+ rails_logger = RAILS_DEFAULT_LOGGER
41
+ end
42
+ end
43
+
44
+ def self.rails_env
45
+ if defined?(::Rails.env)
46
+ ::Rails.env
47
+ elsif defined?(RAILS_ENV)
48
+ RAILS_ENV
49
+ end
50
+ end
51
+
52
+ def self.rails_root
53
+ if defined?(::Rails.root)
54
+ ::Rails.root
55
+ elsif defined?(RAILS_ROOT)
56
+ RAILS_ROOT
57
+ end
42
58
  end
43
59
  end
44
60
  end
@@ -2,6 +2,8 @@
2
2
  module Bugsnag::Rails
3
3
  module ActionControllerRescue
4
4
  def self.included(base)
5
+ base.extend(ClassMethods)
6
+
5
7
  # Hook into rails exception rescue stack
6
8
  base.send(:alias_method, :rescue_action_in_public_without_bugsnag, :rescue_action_in_public)
7
9
  base.send(:alias_method, :rescue_action_in_public, :rescue_action_in_public_with_bugsnag)
@@ -30,5 +32,31 @@ module Bugsnag::Rails
30
32
 
31
33
  rescue_action_locally_without_bugsnag(exception)
32
34
  end
35
+
36
+ module ClassMethods
37
+
38
+ def self.extended(base)
39
+ base.singleton_class.class_eval do
40
+ alias_method_chain :filter_parameter_logging, :bugsnag
41
+ end
42
+ end
43
+
44
+ # Rails 2 does parameter filtering via a controller configuration method
45
+ # that dynamically defines a method on the controller, so the configured
46
+ # parameters aren't easily accessible. Intercept these parameters as
47
+ # they're configured so that the Bugsnag configuration can take them
48
+ # into account.
49
+ #
50
+ def filter_parameter_logging_with_bugsnag(*filter_words, &block)
51
+ if filter_words.length > 0
52
+ Bugsnag.configure do |config|
53
+ # Use the same regular expression that Rails parameter filtering uses.
54
+ config.params_filters << Regexp.new(filter_words.collect{ |s| s.to_s }.join('|'), true)
55
+ end
56
+ end
57
+ filter_parameter_logging_without_bugsnag(*filter_words, &block)
58
+ end
59
+ end
60
+
33
61
  end
34
62
  end