bugsnag-maglev- 2.8.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.gitignore +52 -0
- data/.rspec +3 -0
- data/.travis.yml +15 -0
- data/CHANGELOG.md +425 -0
- data/CONTRIBUTING.md +43 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +20 -0
- data/README.md +804 -0
- data/Rakefile +29 -0
- data/VERSION +1 -0
- data/bugsnag.gemspec +32 -0
- data/lib/bugsnag.rb +129 -0
- data/lib/bugsnag/capistrano.rb +7 -0
- data/lib/bugsnag/capistrano2.rb +32 -0
- data/lib/bugsnag/configuration.rb +129 -0
- data/lib/bugsnag/delay/resque.rb +21 -0
- data/lib/bugsnag/delayed_job.rb +57 -0
- data/lib/bugsnag/delivery.rb +18 -0
- data/lib/bugsnag/delivery/synchronous.rb +51 -0
- data/lib/bugsnag/delivery/thread_queue.rb +53 -0
- data/lib/bugsnag/deploy.rb +35 -0
- data/lib/bugsnag/helpers.rb +127 -0
- data/lib/bugsnag/mailman.rb +28 -0
- data/lib/bugsnag/meta_data.rb +7 -0
- data/lib/bugsnag/middleware/callbacks.rb +19 -0
- data/lib/bugsnag/middleware/mailman.rb +13 -0
- data/lib/bugsnag/middleware/rack_request.rb +72 -0
- data/lib/bugsnag/middleware/rails2_request.rb +52 -0
- data/lib/bugsnag/middleware/rails3_request.rb +42 -0
- data/lib/bugsnag/middleware/rake.rb +23 -0
- data/lib/bugsnag/middleware/sidekiq.rb +13 -0
- data/lib/bugsnag/middleware/warden_user.rb +39 -0
- data/lib/bugsnag/middleware_stack.rb +98 -0
- data/lib/bugsnag/notification.rb +452 -0
- data/lib/bugsnag/rack.rb +53 -0
- data/lib/bugsnag/rails.rb +66 -0
- data/lib/bugsnag/rails/action_controller_rescue.rb +62 -0
- data/lib/bugsnag/rails/active_record_rescue.rb +20 -0
- data/lib/bugsnag/rails/controller_methods.rb +44 -0
- data/lib/bugsnag/railtie.rb +78 -0
- data/lib/bugsnag/rake.rb +25 -0
- data/lib/bugsnag/resque.rb +40 -0
- data/lib/bugsnag/sidekiq.rb +38 -0
- data/lib/bugsnag/tasks.rb +3 -0
- data/lib/bugsnag/tasks/bugsnag.cap +48 -0
- data/lib/bugsnag/tasks/bugsnag.rake +89 -0
- data/lib/bugsnag/version.rb +3 -0
- data/lib/generators/bugsnag/bugsnag_generator.rb +24 -0
- data/rails/init.rb +3 -0
- data/spec/code_spec.rb +86 -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/fixtures/middleware/internal_info_setter.rb +11 -0
- data/spec/fixtures/middleware/public_info_setter.rb +11 -0
- data/spec/fixtures/tasks/Rakefile +15 -0
- data/spec/helper_spec.rb +144 -0
- data/spec/integration_spec.rb +110 -0
- data/spec/middleware_spec.rb +181 -0
- data/spec/notification_spec.rb +822 -0
- data/spec/rack_spec.rb +56 -0
- data/spec/spec_helper.rb +53 -0
- metadata +198 -0
@@ -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,51 @@
|
|
1
|
+
require "net/https"
|
2
|
+
require "uri"
|
3
|
+
|
4
|
+
module Bugsnag
|
5
|
+
module Delivery
|
6
|
+
class Synchronous
|
7
|
+
HEADERS = {"Content-Type" => "application/json"}
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def deliver(url, body, configuration)
|
11
|
+
begin
|
12
|
+
response = request(url, body, configuration)
|
13
|
+
Bugsnag.debug("Notification to #{url} finished, response was #{response.code}, payload was #{body}")
|
14
|
+
rescue StandardError => e
|
15
|
+
# KLUDGE: Since we don't re-raise http exceptions, this breaks rspec
|
16
|
+
raise if e.class.to_s == "RSpec::Expectations::ExpectationNotMetError"
|
17
|
+
|
18
|
+
Bugsnag.warn("Notification to #{url} failed, #{e.inspect}")
|
19
|
+
Bugsnag.warn(e.backtrace)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def request(url, body, configuration)
|
26
|
+
uri = URI.parse(url)
|
27
|
+
http = Net::HTTP.new(uri.host, uri.port, configuration.proxy_host, configuration.proxy_port, configuration.proxy_user, configuration.proxy_password)
|
28
|
+
http.read_timeout = configuration.timeout
|
29
|
+
http.open_timeout = configuration.timeout
|
30
|
+
|
31
|
+
if uri.scheme == "https"
|
32
|
+
http.use_ssl = true
|
33
|
+
# the default in 1.9+, but required for 1.8
|
34
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
35
|
+
http.ca_file = configuration.ca_file if configuration.ca_file
|
36
|
+
end
|
37
|
+
|
38
|
+
request = Net::HTTP::Post.new(path(uri), HEADERS)
|
39
|
+
request.body = body
|
40
|
+
http.request(request)
|
41
|
+
end
|
42
|
+
|
43
|
+
def path(uri)
|
44
|
+
uri.path == "" ? "/" : uri.path
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Bugsnag::Delivery.register(:synchronous, Bugsnag::Delivery::Synchronous)
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
module Bugsnag
|
4
|
+
module Delivery
|
5
|
+
class ThreadQueue < Synchronous
|
6
|
+
MAX_OUTSTANDING_REQUESTS = 100
|
7
|
+
STOP = Object.new
|
8
|
+
MUTEX = Mutex.new
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def deliver(url, body, configuration)
|
12
|
+
start_once!
|
13
|
+
|
14
|
+
if queue.length > MAX_OUTSTANDING_REQUESTS
|
15
|
+
Bugsnag.warn("Dropping notification, #{queue.length} outstanding requests")
|
16
|
+
return
|
17
|
+
end
|
18
|
+
|
19
|
+
# Add delivery to the worker thread
|
20
|
+
queue.push proc { super(url, body, configuration) }
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :queue
|
26
|
+
|
27
|
+
def start_once!
|
28
|
+
MUTEX.synchronize do
|
29
|
+
return if @started
|
30
|
+
@started = true
|
31
|
+
|
32
|
+
@queue = Queue.new
|
33
|
+
|
34
|
+
worker_thread = Thread.new do
|
35
|
+
while x = queue.pop
|
36
|
+
break if x == STOP
|
37
|
+
x.call
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
at_exit do
|
42
|
+
Bugsnag.warn("Waiting for #{queue.length} outstanding request(s)") unless queue.empty?
|
43
|
+
queue.push STOP
|
44
|
+
worker_thread.join
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
Bugsnag::Delivery.register(:thread_queue, Bugsnag::Delivery::ThreadQueue)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "bugsnag"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module Bugsnag
|
5
|
+
class Deploy
|
6
|
+
def self.notify(opts = {})
|
7
|
+
|
8
|
+
configuration = Bugsnag.configuration.dup
|
9
|
+
|
10
|
+
# update configuration based on parameters passed in
|
11
|
+
[:api_key, :app_version, :release_stage, :endpoint, :use_ssl,
|
12
|
+
:proxy_host, :proxy_port, :proxy_user, :proxy_password].each do |param|
|
13
|
+
unless opts[param].nil?
|
14
|
+
configuration.send :"#{param}=", opts[param]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
endpoint = (configuration.use_ssl ? "https://" : "http://") + configuration.endpoint + "/deploy"
|
19
|
+
|
20
|
+
parameters = {
|
21
|
+
"apiKey" => configuration.api_key,
|
22
|
+
"releaseStage" => configuration.release_stage,
|
23
|
+
"appVersion" => configuration.app_version,
|
24
|
+
"revision" => opts[:revision],
|
25
|
+
"repository" => opts[:repository],
|
26
|
+
"branch" => opts[:branch]
|
27
|
+
}.reject {|k,v| v == nil}
|
28
|
+
|
29
|
+
raise RuntimeError.new("No API key found when notifying of deploy") if !parameters["apiKey"] || parameters["apiKey"].empty?
|
30
|
+
|
31
|
+
payload_string = ::JSON.dump(parameters)
|
32
|
+
Bugsnag::Delivery::Synchronous.deliver(endpoint, payload_string, configuration)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Bugsnag
|
4
|
+
module Helpers
|
5
|
+
MAX_STRING_LENGTH = 4096
|
6
|
+
ENCODING_OPTIONS = {:invalid => :replace, :undef => :replace}.freeze
|
7
|
+
|
8
|
+
def self.cleanup_obj(obj, filters = nil, seen = {})
|
9
|
+
return nil unless obj
|
10
|
+
|
11
|
+
# Protect against recursion of recursable items
|
12
|
+
protection = if obj.is_a?(Hash) || obj.is_a?(Array) || obj.is_a?(Set)
|
13
|
+
return seen[obj] if seen[obj]
|
14
|
+
seen[obj] = '[RECURSION]'.freeze
|
15
|
+
end
|
16
|
+
|
17
|
+
value = case obj
|
18
|
+
when Hash
|
19
|
+
clean_hash = {}
|
20
|
+
obj.each do |k,v|
|
21
|
+
if filters_match?(k, filters)
|
22
|
+
clean_hash[k] = '[FILTERED]'.freeze
|
23
|
+
else
|
24
|
+
clean_obj = cleanup_obj(v, filters, seen)
|
25
|
+
clean_hash[k] = clean_obj
|
26
|
+
end
|
27
|
+
end
|
28
|
+
clean_hash
|
29
|
+
when Array, Set
|
30
|
+
obj.map { |el| cleanup_obj(el, filters, seen) }.compact
|
31
|
+
when Numeric, TrueClass, FalseClass
|
32
|
+
obj
|
33
|
+
when String
|
34
|
+
cleanup_string(obj)
|
35
|
+
else
|
36
|
+
str = obj.to_s
|
37
|
+
# avoid leaking potentially sensitive data from objects' #inspect output
|
38
|
+
if str =~ /#<.*>/
|
39
|
+
'[OBJECT]'.freeze
|
40
|
+
else
|
41
|
+
cleanup_string(str)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
seen[obj] = value if protection
|
46
|
+
value
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.cleanup_string(str)
|
50
|
+
if defined?(str.encoding) && defined?(Encoding::UTF_8)
|
51
|
+
if str.encoding == Encoding::UTF_8
|
52
|
+
str.valid_encoding? ? str : str.encode('utf-16', ENCODING_OPTIONS).encode('utf-8')
|
53
|
+
else
|
54
|
+
str.encode('utf-8', ENCODING_OPTIONS)
|
55
|
+
end
|
56
|
+
elsif defined?(Iconv)
|
57
|
+
Iconv.conv('UTF-8//IGNORE', 'UTF-8', str) || str
|
58
|
+
else
|
59
|
+
str
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.cleanup_obj_encoding(obj)
|
64
|
+
cleanup_obj(obj, nil)
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.filters_match?(object, filters)
|
68
|
+
str = object.to_s
|
69
|
+
|
70
|
+
Array(filters).any? do |f|
|
71
|
+
case f
|
72
|
+
when Regexp
|
73
|
+
str.match(f)
|
74
|
+
else
|
75
|
+
str.include?(f.to_s)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.cleanup_url(url, filters = [])
|
81
|
+
return url if filters.empty?
|
82
|
+
|
83
|
+
uri = URI(url)
|
84
|
+
return url unless uri.query
|
85
|
+
|
86
|
+
query_params = uri.query.split('&').map { |pair| pair.split('=') }
|
87
|
+
query_params.map! do |key, val|
|
88
|
+
if filters_match?(key, filters)
|
89
|
+
"#{key}=[FILTERED]"
|
90
|
+
else
|
91
|
+
"#{key}=#{val}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
uri.query = query_params.join('&')
|
96
|
+
uri.to_s
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.reduce_hash_size(hash)
|
100
|
+
return {} unless hash.is_a?(Hash)
|
101
|
+
hash.inject({}) do |h, (k,v)|
|
102
|
+
if v.is_a?(Hash)
|
103
|
+
h[k] = reduce_hash_size(v)
|
104
|
+
elsif v.is_a?(Array) || v.is_a?(Set)
|
105
|
+
h[k] = v.map {|el| reduce_hash_size(el) }
|
106
|
+
else
|
107
|
+
val = v.to_s
|
108
|
+
val = val.slice(0, MAX_STRING_LENGTH) + "[TRUNCATED]" if val.length > MAX_STRING_LENGTH
|
109
|
+
h[k] = val
|
110
|
+
end
|
111
|
+
|
112
|
+
h
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.flatten_meta_data(overrides)
|
117
|
+
return nil unless overrides
|
118
|
+
|
119
|
+
meta_data = overrides.delete(:meta_data)
|
120
|
+
if meta_data.is_a?(Hash)
|
121
|
+
overrides.merge(meta_data)
|
122
|
+
else
|
123
|
+
overrides
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'mailman'
|
2
|
+
|
3
|
+
module Bugsnag
|
4
|
+
class Mailman
|
5
|
+
def call(mail)
|
6
|
+
begin
|
7
|
+
|
8
|
+
Bugsnag.set_request_data :mailman_msg, mail.to_s
|
9
|
+
|
10
|
+
yield
|
11
|
+
rescue Exception => ex
|
12
|
+
raise ex if [Interrupt, SystemExit, SignalException].include? ex.class
|
13
|
+
Bugsnag.auto_notify(ex)
|
14
|
+
raise
|
15
|
+
ensure
|
16
|
+
Bugsnag.clear_request_data
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
if Mailman.config.respond_to?(:middleware)
|
24
|
+
Mailman.config.middleware.add ::Bugsnag::Mailman
|
25
|
+
end
|
26
|
+
|
27
|
+
Bugsnag.configuration.internal_middleware.use(Bugsnag::Middleware::Mailman)
|
28
|
+
Bugsnag.configuration.app_type = "mailman"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Bugsnag::Middleware
|
2
|
+
class Callbacks
|
3
|
+
def initialize(bugsnag)
|
4
|
+
@bugsnag = bugsnag
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(notification)
|
8
|
+
if notification.request_data[:before_callbacks]
|
9
|
+
notification.request_data[:before_callbacks].each {|c| c.call(*[notification][0...c.arity]) }
|
10
|
+
end
|
11
|
+
|
12
|
+
@bugsnag.call(notification)
|
13
|
+
|
14
|
+
if notification.request_data[:after_callbacks]
|
15
|
+
notification.request_data[:after_callbacks].each {|c| c.call(*[notification][0...c.arity]) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Bugsnag::Middleware
|
2
|
+
class Mailman
|
3
|
+
def initialize(bugsnag)
|
4
|
+
@bugsnag = bugsnag
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(notification)
|
8
|
+
mailman_msg = notification.request_data[:mailman_msg]
|
9
|
+
notification.add_tab(:mailman, {"message" => mailman_msg}) if mailman_msg
|
10
|
+
@bugsnag.call(notification)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Bugsnag::Middleware
|
2
|
+
class RackRequest
|
3
|
+
def initialize(bugsnag)
|
4
|
+
@bugsnag = bugsnag
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(notification)
|
8
|
+
if notification.request_data[:rack_env]
|
9
|
+
env = notification.request_data[:rack_env]
|
10
|
+
|
11
|
+
request = ::Rack::Request.new(env)
|
12
|
+
|
13
|
+
params = request.params rescue {}
|
14
|
+
|
15
|
+
session = env["rack.session"]
|
16
|
+
|
17
|
+
# Set the context
|
18
|
+
notification.context = "#{request.request_method} #{request.path}"
|
19
|
+
|
20
|
+
# Set a sensible default for user_id
|
21
|
+
notification.user_id = request.ip
|
22
|
+
|
23
|
+
# Build the clean url (hide the port if it is obvious)
|
24
|
+
url = "#{request.scheme}://#{request.host}"
|
25
|
+
url << ":#{request.port}" unless [80, 443].include?(request.port)
|
26
|
+
url << Bugsnag::Helpers.cleanup_url(request.fullpath, notification.configuration.params_filters)
|
27
|
+
|
28
|
+
headers = {}
|
29
|
+
|
30
|
+
env.each_pair do |key, value|
|
31
|
+
if key.to_s.start_with?("HTTP_")
|
32
|
+
header_key = key[5..-1]
|
33
|
+
elsif ["CONTENT_TYPE", "CONTENT_LENGTH"].include?(key)
|
34
|
+
header_key = key
|
35
|
+
else
|
36
|
+
next
|
37
|
+
end
|
38
|
+
|
39
|
+
headers[header_key.split("_").map {|s| s.capitalize}.join("-")] = value
|
40
|
+
end
|
41
|
+
|
42
|
+
# Add a request tab
|
43
|
+
notification.add_tab(:request, {
|
44
|
+
:url => url,
|
45
|
+
:httpMethod => request.request_method,
|
46
|
+
:params => params.to_hash,
|
47
|
+
:referer => request.referer,
|
48
|
+
:clientIp => request.ip,
|
49
|
+
:headers => headers
|
50
|
+
})
|
51
|
+
|
52
|
+
# Add an environment tab
|
53
|
+
if notification.configuration.send_environment
|
54
|
+
notification.add_tab(:environment, env)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Add a session tab
|
58
|
+
if session
|
59
|
+
if session.is_a?(Hash)
|
60
|
+
# Rails 3
|
61
|
+
notification.add_tab(:session, session)
|
62
|
+
elsif session.respond_to?(:to_hash)
|
63
|
+
# Rails 4
|
64
|
+
notification.add_tab(:session, session.to_hash)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
@bugsnag.call(notification)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Bugsnag::Middleware
|
2
|
+
class Rails2Request
|
3
|
+
def initialize(bugsnag)
|
4
|
+
@bugsnag = bugsnag
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(notification)
|
8
|
+
if notification.request_data[:rails2_request]
|
9
|
+
request = notification.request_data[:rails2_request]
|
10
|
+
params = request.parameters || {}
|
11
|
+
session_data = request.session.respond_to?(:to_hash) ? request.session.to_hash : request.session.data
|
12
|
+
|
13
|
+
# Set the context
|
14
|
+
notification.context = "#{params[:controller]}##{params[:action]}"
|
15
|
+
|
16
|
+
# Set a sensible default for user_id
|
17
|
+
notification.user_id = request.remote_ip if request.respond_to?(:remote_ip)
|
18
|
+
|
19
|
+
# Build the clean url
|
20
|
+
url = "#{request.protocol}#{request.host}"
|
21
|
+
url << ":#{request.port}" unless [80, 443].include?(request.port)
|
22
|
+
url << Bugsnag::Helpers.cleanup_url(request.fullpath, notification.configuration.params_filters)
|
23
|
+
|
24
|
+
# Add a request tab
|
25
|
+
notification.add_tab(:request, {
|
26
|
+
:url => url,
|
27
|
+
:params => params.to_hash,
|
28
|
+
:controller => params[:controller],
|
29
|
+
:action => params[:action]
|
30
|
+
})
|
31
|
+
|
32
|
+
# Add an environment tab
|
33
|
+
if request.env && notification.configuration.send_environment
|
34
|
+
notification.add_tab(:environment, request.env)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Add a session tab
|
38
|
+
notification.add_tab(:session, session_data) if session_data
|
39
|
+
|
40
|
+
# Add a cookies tab
|
41
|
+
notification.add_tab(:cookies, request.cookies) if request.cookies
|
42
|
+
|
43
|
+
# Add the rails version
|
44
|
+
notification.add_tab(:environment, {
|
45
|
+
:railsVersion => Rails::VERSION::STRING
|
46
|
+
})
|
47
|
+
end
|
48
|
+
|
49
|
+
@bugsnag.call(notification)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|