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