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.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gitignore +52 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +15 -0
  6. data/CHANGELOG.md +425 -0
  7. data/CONTRIBUTING.md +43 -0
  8. data/Gemfile +2 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.md +804 -0
  11. data/Rakefile +29 -0
  12. data/VERSION +1 -0
  13. data/bugsnag.gemspec +32 -0
  14. data/lib/bugsnag.rb +129 -0
  15. data/lib/bugsnag/capistrano.rb +7 -0
  16. data/lib/bugsnag/capistrano2.rb +32 -0
  17. data/lib/bugsnag/configuration.rb +129 -0
  18. data/lib/bugsnag/delay/resque.rb +21 -0
  19. data/lib/bugsnag/delayed_job.rb +57 -0
  20. data/lib/bugsnag/delivery.rb +18 -0
  21. data/lib/bugsnag/delivery/synchronous.rb +51 -0
  22. data/lib/bugsnag/delivery/thread_queue.rb +53 -0
  23. data/lib/bugsnag/deploy.rb +35 -0
  24. data/lib/bugsnag/helpers.rb +127 -0
  25. data/lib/bugsnag/mailman.rb +28 -0
  26. data/lib/bugsnag/meta_data.rb +7 -0
  27. data/lib/bugsnag/middleware/callbacks.rb +19 -0
  28. data/lib/bugsnag/middleware/mailman.rb +13 -0
  29. data/lib/bugsnag/middleware/rack_request.rb +72 -0
  30. data/lib/bugsnag/middleware/rails2_request.rb +52 -0
  31. data/lib/bugsnag/middleware/rails3_request.rb +42 -0
  32. data/lib/bugsnag/middleware/rake.rb +23 -0
  33. data/lib/bugsnag/middleware/sidekiq.rb +13 -0
  34. data/lib/bugsnag/middleware/warden_user.rb +39 -0
  35. data/lib/bugsnag/middleware_stack.rb +98 -0
  36. data/lib/bugsnag/notification.rb +452 -0
  37. data/lib/bugsnag/rack.rb +53 -0
  38. data/lib/bugsnag/rails.rb +66 -0
  39. data/lib/bugsnag/rails/action_controller_rescue.rb +62 -0
  40. data/lib/bugsnag/rails/active_record_rescue.rb +20 -0
  41. data/lib/bugsnag/rails/controller_methods.rb +44 -0
  42. data/lib/bugsnag/railtie.rb +78 -0
  43. data/lib/bugsnag/rake.rb +25 -0
  44. data/lib/bugsnag/resque.rb +40 -0
  45. data/lib/bugsnag/sidekiq.rb +38 -0
  46. data/lib/bugsnag/tasks.rb +3 -0
  47. data/lib/bugsnag/tasks/bugsnag.cap +48 -0
  48. data/lib/bugsnag/tasks/bugsnag.rake +89 -0
  49. data/lib/bugsnag/version.rb +3 -0
  50. data/lib/generators/bugsnag/bugsnag_generator.rb +24 -0
  51. data/rails/init.rb +3 -0
  52. data/spec/code_spec.rb +86 -0
  53. data/spec/fixtures/crashes/end_of_file.rb +9 -0
  54. data/spec/fixtures/crashes/short_file.rb +1 -0
  55. data/spec/fixtures/crashes/start_of_file.rb +9 -0
  56. data/spec/fixtures/middleware/internal_info_setter.rb +11 -0
  57. data/spec/fixtures/middleware/public_info_setter.rb +11 -0
  58. data/spec/fixtures/tasks/Rakefile +15 -0
  59. data/spec/helper_spec.rb +144 -0
  60. data/spec/integration_spec.rb +110 -0
  61. data/spec/middleware_spec.rb +181 -0
  62. data/spec/notification_spec.rb +822 -0
  63. data/spec/rack_spec.rb +56 -0
  64. data/spec/spec_helper.rb +53 -0
  65. 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,7 @@
1
+ module Bugsnag
2
+ module MetaData
3
+ attr_accessor :bugsnag_meta_data
4
+ attr_accessor :bugsnag_user_id
5
+ attr_accessor :bugsnag_context
6
+ end
7
+ end
@@ -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