bugsnag-maglev- 2.8.12
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 +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
|