bugsnag-maglev- 2.8.12

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