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 +4 -4
- data/CHANGELOG.md +9 -1
- data/README.md +10 -0
- data/VERSION +1 -1
- data/bugsnag.gemspec +1 -0
- data/lib/bugsnag.rb +4 -1
- data/lib/bugsnag/configuration.rb +6 -0
- data/lib/bugsnag/delivery.rb +18 -0
- data/lib/bugsnag/delivery/synchronous.rb +25 -0
- data/lib/bugsnag/delivery/thread_queue.rb +51 -0
- data/lib/bugsnag/notification.rb +55 -37
- data/lib/bugsnag/rack.rb +2 -2
- data/lib/bugsnag/rails.rb +28 -12
- data/lib/bugsnag/rails/action_controller_rescue.rb +28 -0
- data/lib/bugsnag/rake.rb +2 -0
- data/lib/bugsnag/resque.rb +1 -1
- data/spec/code_spec.rb +96 -0
- data/spec/fixtures/crashes/end_of_file.rb +9 -0
- data/spec/fixtures/crashes/short_file.rb +1 -0
- data/spec/fixtures/crashes/start_of_file.rb +9 -0
- data/spec/integration_spec.rb +1 -0
- data/spec/middleware_spec.rb +41 -39
- data/spec/notification_spec.rb +281 -275
- data/spec/rack_spec.rb +10 -9
- data/spec/spec_helper.rb +26 -6
- metadata +23 -3
- data/lib/bugsnag/queue.rb +0 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ef55d682c90467747f2be118b83bd3a85b156b1d
|
4
|
+
data.tar.gz: 62d482b4c71148dfe0713ea33df2289bb1f5c1fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
-
|
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.
|
1
|
+
2.6.0
|
data/bugsnag.gemspec
CHANGED
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)
|
data/lib/bugsnag/notification.rb
CHANGED
@@ -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(
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
Bugsnag.
|
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
|
-
|
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
|
-
|
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 =
|
33
|
-
config.project_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(
|
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[
|
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
|