raygun4ruby 3.1.1 → 3.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +18 -18
  3. data/.rspec +1 -1
  4. data/.travis.yml +20 -12
  5. data/CHANGELOG.md +127 -109
  6. data/Gemfile +4 -4
  7. data/LICENSE.txt +22 -22
  8. data/README.md +420 -420
  9. data/Rakefile +27 -27
  10. data/examples/sinatras_raygun.rb +17 -17
  11. data/lib/generators/raygun/install_generator.rb +26 -26
  12. data/lib/raygun.rb +179 -182
  13. data/lib/raygun/affected_user.rb +59 -59
  14. data/lib/raygun/breadcrumbs.rb +34 -34
  15. data/lib/raygun/breadcrumbs/breadcrumb.rb +34 -30
  16. data/lib/raygun/breadcrumbs/store.rb +86 -76
  17. data/lib/raygun/client.rb +305 -302
  18. data/lib/raygun/configuration.rb +194 -194
  19. data/lib/raygun/error.rb +10 -10
  20. data/lib/raygun/javascript_tracker.rb +42 -42
  21. data/lib/raygun/middleware/breadcrumbs_store_initializer.rb +19 -19
  22. data/lib/raygun/middleware/javascript_exception_tracking.rb +32 -32
  23. data/lib/raygun/middleware/rack_exception_interceptor.rb +18 -18
  24. data/lib/raygun/middleware/rails_insert_affected_user.rb +26 -26
  25. data/lib/raygun/railtie.rb +39 -39
  26. data/lib/raygun/services/apply_whitelist_filter_to_payload.rb +27 -27
  27. data/lib/raygun/sidekiq.rb +71 -67
  28. data/lib/raygun/testable.rb +22 -22
  29. data/lib/raygun/version.rb +3 -3
  30. data/lib/raygun4ruby.rb +1 -1
  31. data/lib/resque/failure/raygun.rb +25 -25
  32. data/lib/tasks/raygun.tasks +7 -7
  33. data/raygun4ruby.gemspec +45 -45
  34. data/spec/dummy/.gitignore +17 -0
  35. data/spec/dummy/Gemfile +47 -0
  36. data/spec/dummy/README.rdoc +28 -0
  37. data/spec/dummy/Rakefile +6 -6
  38. data/spec/dummy/app/assets/config/manifest.js +3 -3
  39. data/spec/dummy/app/assets/images/.keep +0 -0
  40. data/spec/dummy/app/assets/javascripts/application.js +13 -15
  41. data/spec/dummy/app/assets/stylesheets/application.css +15 -15
  42. data/spec/dummy/app/controllers/application_controller.rb +5 -2
  43. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  44. data/spec/dummy/app/controllers/home_controller.rb +4 -4
  45. data/spec/dummy/app/helpers/application_helper.rb +2 -2
  46. data/spec/dummy/app/{assets/javascripts/channels → mailers}/.keep +0 -0
  47. data/spec/dummy/{storage → app/models}/.keep +0 -0
  48. data/spec/dummy/app/models/concerns/.keep +0 -0
  49. data/spec/dummy/app/views/home/index.html.erb +3 -3
  50. data/spec/dummy/app/views/home/index.json.erb +1 -1
  51. data/spec/dummy/app/views/layouts/application.html.erb +14 -15
  52. data/spec/dummy/bin/bundle +3 -3
  53. data/spec/dummy/bin/rails +9 -4
  54. data/spec/dummy/bin/rake +9 -4
  55. data/spec/dummy/bin/setup +29 -36
  56. data/spec/dummy/bin/spring +17 -0
  57. data/spec/dummy/config.ru +4 -5
  58. data/spec/dummy/config/application.rb +26 -18
  59. data/spec/dummy/config/boot.rb +3 -5
  60. data/spec/dummy/config/database.yml +25 -25
  61. data/spec/dummy/config/environment.rb +5 -5
  62. data/spec/dummy/config/environments/development.rb +41 -61
  63. data/spec/dummy/config/environments/production.rb +79 -94
  64. data/spec/dummy/config/environments/test.rb +42 -46
  65. data/spec/dummy/config/initializers/assets.rb +11 -14
  66. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -7
  67. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -5
  68. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -4
  69. data/spec/dummy/config/initializers/inflections.rb +16 -16
  70. data/spec/dummy/config/initializers/mime_types.rb +4 -4
  71. data/spec/dummy/config/initializers/session_store.rb +3 -0
  72. data/spec/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
  73. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -14
  74. data/spec/dummy/config/locales/en.yml +23 -33
  75. data/spec/dummy/config/routes.rb +58 -3
  76. data/spec/dummy/config/secrets.yml +22 -0
  77. data/spec/dummy/db/seeds.rb +7 -0
  78. data/spec/dummy/db/test.sqlite3 +0 -0
  79. data/spec/dummy/lib/assets/.keep +0 -0
  80. data/spec/dummy/{public/apple-touch-icon-precomposed.png → lib/tasks/.keep} +0 -0
  81. data/spec/dummy/public/404.html +67 -67
  82. data/spec/dummy/public/422.html +67 -67
  83. data/spec/dummy/public/500.html +66 -66
  84. data/spec/dummy/public/favicon.ico +0 -0
  85. data/spec/dummy/public/robots.txt +5 -0
  86. data/spec/dummy/{public/apple-touch-icon.png → test/controllers/.keep} +0 -0
  87. data/spec/dummy/test/fixtures/.keep +0 -0
  88. data/spec/dummy/test/helpers/.keep +0 -0
  89. data/spec/dummy/test/integration/.keep +0 -0
  90. data/spec/dummy/test/mailers/.keep +0 -0
  91. data/spec/dummy/test/models/.keep +0 -0
  92. data/spec/dummy/test/test_helper.rb +10 -0
  93. data/spec/dummy/vendor/assets/javascripts/.keep +0 -0
  94. data/spec/dummy/vendor/assets/stylesheets/.keep +0 -0
  95. data/spec/features/javascript_spec.rb +48 -48
  96. data/spec/rails_helper.rb +4 -4
  97. data/spec/raygun/breadcrumbs/breadcrumb_spec.rb +171 -134
  98. data/spec/raygun/breadcrumbs/store_spec.rb +170 -146
  99. data/spec/raygun/raygun_spec.rb +47 -47
  100. data/spec/services/apply_whitelist_filter_to_payload_spec.rb +251 -251
  101. data/spec/spec_helper.rb +24 -24
  102. data/spec/support/fake_logger.rb +17 -17
  103. data/test/integration/client_test.rb +19 -19
  104. data/test/test_helper.rb +72 -72
  105. data/test/unit/affected_user_test.rb +136 -136
  106. data/test/unit/client_test.rb +792 -790
  107. data/test/unit/configuration_test.rb +206 -206
  108. data/test/unit/raygun_test.rb +25 -25
  109. data/test/unit/resque_failure_test.rb +24 -24
  110. data/test/unit/sidekiq_failure_test.rb +32 -32
  111. metadata +42 -45
  112. data/spec/dummy/.ruby-version +0 -1
  113. data/spec/dummy/app/assets/javascripts/cable.js +0 -13
  114. data/spec/dummy/app/channels/application_cable/channel.rb +0 -4
  115. data/spec/dummy/app/channels/application_cable/connection.rb +0 -4
  116. data/spec/dummy/app/jobs/application_job.rb +0 -2
  117. data/spec/dummy/app/mailers/application_mailer.rb +0 -4
  118. data/spec/dummy/app/models/application_record.rb +0 -3
  119. data/spec/dummy/app/views/layouts/mailer.html.erb +0 -13
  120. data/spec/dummy/app/views/layouts/mailer.text.erb +0 -1
  121. data/spec/dummy/bin/update +0 -31
  122. data/spec/dummy/bin/yarn +0 -11
  123. data/spec/dummy/config/cable.yml +0 -10
  124. data/spec/dummy/config/initializers/application_controller_renderer.rb +0 -8
  125. data/spec/dummy/config/initializers/content_security_policy.rb +0 -25
  126. data/spec/dummy/config/puma.rb +0 -34
  127. data/spec/dummy/config/spring.rb +0 -6
  128. data/spec/dummy/config/storage.yml +0 -34
  129. data/spec/dummy/db/schema.rb +0 -15
  130. data/spec/dummy/package.json +0 -5
@@ -1,59 +1,59 @@
1
- module Raygun
2
- class AffectedUser
3
-
4
- DEFAULT_MAPPING = {
5
- identifier: [ :id, :username ],
6
- email: :email,
7
- full_name: [ :full_name, :name ],
8
- first_name: :first_name,
9
- uuid: :uuid
10
- }.freeze
11
- SUPPORTED_ATTRIBUTES = DEFAULT_MAPPING.keys.freeze
12
- NAME_TO_RAYGUN_NAME_MAPPING = {
13
- identifier: :identifier,
14
- email: :email,
15
- full_name: :fullName,
16
- first_name: :firstName,
17
- uuid: :uuid
18
- }.freeze
19
-
20
- class << self
21
- def information_hash(user_object)
22
- if user_object.nil? || user_object.is_a?(String)
23
- handle_anonymous_user(user_object)
24
- else
25
- handle_known_user(user_object)
26
- end
27
- end
28
-
29
- private
30
-
31
- def handle_anonymous_user(user_object)
32
- result = { isAnonymous: true }
33
- result[:identifier] = user_object unless user_object.nil?
34
- result
35
- end
36
-
37
- def handle_known_user(user_object)
38
- SUPPORTED_ATTRIBUTES.reduce({ isAnonymous: false }) do |result, attribute|
39
- mapping = Raygun.configuration.affected_user_mapping
40
- method = mapping[attribute]
41
-
42
- value = if method.is_a? Proc
43
- method.call(user_object)
44
- else
45
- attributes = Array(method)
46
- attribute_to_use = attributes.select do |attr|
47
- user_object.respond_to?(attr, true)
48
- end.first
49
-
50
- user_object.send(attribute_to_use) unless attribute_to_use == nil
51
- end
52
-
53
- result[NAME_TO_RAYGUN_NAME_MAPPING[attribute]] = value unless value == nil
54
- result
55
- end
56
- end
57
- end
58
- end
59
- end
1
+ module Raygun
2
+ class AffectedUser
3
+
4
+ DEFAULT_MAPPING = {
5
+ identifier: [ :id, :username ],
6
+ email: :email,
7
+ full_name: [ :full_name, :name ],
8
+ first_name: :first_name,
9
+ uuid: :uuid
10
+ }.freeze
11
+ SUPPORTED_ATTRIBUTES = DEFAULT_MAPPING.keys.freeze
12
+ NAME_TO_RAYGUN_NAME_MAPPING = {
13
+ identifier: :identifier,
14
+ email: :email,
15
+ full_name: :fullName,
16
+ first_name: :firstName,
17
+ uuid: :uuid
18
+ }.freeze
19
+
20
+ class << self
21
+ def information_hash(user_object)
22
+ if user_object.nil? || user_object.is_a?(String)
23
+ handle_anonymous_user(user_object)
24
+ else
25
+ handle_known_user(user_object)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def handle_anonymous_user(user_object)
32
+ result = { isAnonymous: true }
33
+ result[:identifier] = user_object unless user_object.nil?
34
+ result
35
+ end
36
+
37
+ def handle_known_user(user_object)
38
+ SUPPORTED_ATTRIBUTES.reduce({ isAnonymous: false }) do |result, attribute|
39
+ mapping = Raygun.configuration.affected_user_mapping
40
+ method = mapping[attribute]
41
+
42
+ value = if method.is_a? Proc
43
+ method.call(user_object)
44
+ else
45
+ attributes = Array(method)
46
+ attribute_to_use = attributes.select do |attr|
47
+ user_object.respond_to?(attr, true)
48
+ end.first
49
+
50
+ user_object.send(attribute_to_use) unless attribute_to_use == nil
51
+ end
52
+
53
+ result[NAME_TO_RAYGUN_NAME_MAPPING[attribute]] = value unless value == nil
54
+ result
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,34 +1,34 @@
1
- module Raygun
2
- module Breadcrumbs
3
- BREADCRUMB_LEVELS = [
4
- :debug,
5
- :info,
6
- :warning,
7
- :error,
8
- :fatal
9
- ]
10
-
11
- def record_breadcrumb(
12
- message: nil,
13
- category: '',
14
- level: :info,
15
- timestamp: Time.now.utc.to_i,
16
- metadata: {},
17
- class_name: nil,
18
- method_name: nil,
19
- line_number: nil
20
- )
21
- class_name = class_name || self.class.name
22
- Raygun::Breadcrumbs::Store.record(
23
- message: message,
24
- category: category,
25
- level: level,
26
- timestamp: timestamp,
27
- metadata: metadata,
28
- class_name: class_name,
29
- method_name: method_name,
30
- line_number: line_number,
31
- )
32
- end
33
- end
34
- end
1
+ module Raygun
2
+ module Breadcrumbs
3
+ BREADCRUMB_LEVELS = [
4
+ :debug,
5
+ :info,
6
+ :warning,
7
+ :error,
8
+ :fatal
9
+ ]
10
+
11
+ def record_breadcrumb(
12
+ message: nil,
13
+ category: '',
14
+ level: :info,
15
+ timestamp: Time.now.utc.to_i,
16
+ metadata: {},
17
+ class_name: nil,
18
+ method_name: nil,
19
+ line_number: nil
20
+ )
21
+ class_name = class_name || self.class.name
22
+ Raygun::Breadcrumbs::Store.record(
23
+ message: message,
24
+ category: category,
25
+ level: level,
26
+ timestamp: timestamp,
27
+ metadata: metadata,
28
+ class_name: class_name,
29
+ method_name: method_name,
30
+ line_number: line_number,
31
+ )
32
+ end
33
+ end
34
+ end
@@ -1,30 +1,34 @@
1
- module Raygun
2
- module Breadcrumbs
3
- class Breadcrumb
4
- ATTRIBUTES = [
5
- :message, :category, :metadata, :class_name,
6
- :method_name, :line_number, :timestamp, :level,
7
- :type
8
- ]
9
- attr_accessor(*ATTRIBUTES)
10
-
11
- def build_payload
12
- payload = {
13
- message: message,
14
- category: category,
15
- level: Breadcrumbs::BREADCRUMB_LEVELS.index(level),
16
- CustomData: metadata,
17
- timestamp: timestamp,
18
- type: type
19
- }
20
-
21
- payload[:location] = "#{class_name}:#{method_name}" unless class_name == nil
22
- payload[:location] += ":#{line_number}" if payload.has_key?(:location) && line_number != nil
23
-
24
- Hash[payload.select do |k, v|
25
- v != nil
26
- end]
27
- end
28
- end
29
- end
30
- end
1
+ module Raygun
2
+ module Breadcrumbs
3
+ class Breadcrumb
4
+ ATTRIBUTES = [
5
+ :message, :category, :metadata, :class_name,
6
+ :method_name, :line_number, :timestamp, :level,
7
+ :type
8
+ ]
9
+ attr_accessor(*ATTRIBUTES)
10
+
11
+ def build_payload
12
+ payload = {
13
+ message: message,
14
+ category: category,
15
+ level: Breadcrumbs::BREADCRUMB_LEVELS.index(level),
16
+ CustomData: metadata,
17
+ timestamp: timestamp,
18
+ type: type
19
+ }
20
+
21
+ payload[:location] = "#{class_name}:#{method_name}" unless class_name == nil
22
+ payload[:location] += ":#{line_number}" if payload.has_key?(:location) && line_number != nil
23
+
24
+ Hash[payload.select do |k, v|
25
+ v != nil
26
+ end]
27
+ end
28
+
29
+ def size
30
+ return message.length + 100
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,76 +1,86 @@
1
- require_relative 'breadcrumb'
2
-
3
- module Raygun
4
- module Breadcrumbs
5
- class Store
6
- def self.initialize
7
- Thread.current[:breadcrumbs] ||= []
8
- end
9
-
10
- def self.clear
11
- Thread.current[:breadcrumbs] = nil
12
- end
13
-
14
- def self.stored
15
- Thread.current[:breadcrumbs]
16
- end
17
-
18
- def self.record(
19
- message: nil,
20
- category: '',
21
- level: :info,
22
- timestamp: Time.now.utc.to_i,
23
- metadata: {},
24
- class_name: nil,
25
- method_name: nil,
26
- line_number: nil
27
- )
28
- raise ArgumentError.new('missing keyword: message') if message == nil
29
- crumb = Breadcrumb.new
30
-
31
- crumb.message = message
32
- crumb.category = category
33
- crumb.level = level
34
- crumb.metadata = metadata
35
- crumb.timestamp = timestamp
36
- crumb.type = 'manual'
37
-
38
- caller = caller_locations[1]
39
- crumb.class_name = class_name
40
- crumb.method_name = method_name || caller.label
41
- crumb.line_number = line_number || caller.lineno
42
-
43
- Thread.current[:breadcrumbs] << crumb if should_record?(crumb)
44
- end
45
-
46
- def self.any?
47
- stored != nil && stored.length > 0
48
- end
49
-
50
- private
51
-
52
- def self.should_record?(crumb)
53
- if stored.nil?
54
- if Raygun.configuration.debug
55
- Raygun.log('[Raygun.breadcrumbs] store is uninitialized while breadcrumb is being recorded, discarding breadcrumb')
56
- end
57
-
58
- return false
59
- end
60
-
61
- levels = Raygun::Breadcrumbs::BREADCRUMB_LEVELS
62
-
63
- active_level = levels.index(Raygun.configuration.breadcrumb_level)
64
- crumb_level = levels.index(crumb.level) || -1
65
-
66
- discard = crumb_level < active_level
67
-
68
- if discard && Raygun.configuration.debug
69
- Raygun.log("[Raygun.breadcrumbs] discarding breadcrumb because #{crumb.level} is below active breadcrumb level (#{Raygun.configuration.breadcrumb_level})")
70
- end
71
-
72
- !discard
73
- end
74
- end
75
- end
76
- end
1
+ require_relative 'breadcrumb'
2
+
3
+ module Raygun
4
+ module Breadcrumbs
5
+ class Store
6
+ def self.initialize
7
+ Thread.current[:breadcrumbs] ||= []
8
+ end
9
+
10
+ def self.clear
11
+ Thread.current[:breadcrumbs] = nil
12
+ end
13
+
14
+ def self.stored
15
+ Thread.current[:breadcrumbs]
16
+ end
17
+
18
+ def self.record(
19
+ message: nil,
20
+ category: '',
21
+ level: :info,
22
+ timestamp: Time.now.utc.to_i,
23
+ metadata: {},
24
+ class_name: nil,
25
+ method_name: nil,
26
+ line_number: nil
27
+ )
28
+ raise ArgumentError.new('missing keyword: message') if message == nil
29
+ crumb = Breadcrumb.new
30
+
31
+ crumb.message = message
32
+ crumb.category = category
33
+ crumb.level = level
34
+ crumb.metadata = metadata
35
+ crumb.timestamp = timestamp
36
+ crumb.type = 'manual'
37
+
38
+ caller = caller_locations[1]
39
+ crumb.class_name = class_name
40
+ crumb.method_name = method_name || caller.label
41
+ crumb.line_number = line_number || caller.lineno
42
+
43
+ Thread.current[:breadcrumbs] << crumb if should_record?(crumb)
44
+ end
45
+
46
+ def self.any?
47
+ stored != nil && stored.length > 0
48
+ end
49
+
50
+ def self.take_until_size(size)
51
+ breadcrumb_size = 0
52
+
53
+ stored.reverse.take_while do |crumb|
54
+ breadcrumb_size += crumb.size
55
+
56
+ breadcrumb_size < size
57
+ end.reverse
58
+ end
59
+
60
+ private
61
+
62
+ def self.should_record?(crumb)
63
+ if stored.nil?
64
+ if Raygun.configuration.debug
65
+ Raygun.log('[Raygun.breadcrumbs] store is uninitialized while breadcrumb is being recorded, discarding breadcrumb')
66
+ end
67
+
68
+ return false
69
+ end
70
+
71
+ levels = Raygun::Breadcrumbs::BREADCRUMB_LEVELS
72
+
73
+ active_level = levels.index(Raygun.configuration.breadcrumb_level)
74
+ crumb_level = levels.index(crumb.level) || -1
75
+
76
+ discard = crumb_level < active_level
77
+
78
+ if discard && Raygun.configuration.debug
79
+ Raygun.log("[Raygun.breadcrumbs] discarding breadcrumb because #{crumb.level} is below active breadcrumb level (#{Raygun.configuration.breadcrumb_level})")
80
+ end
81
+
82
+ !discard
83
+ end
84
+ end
85
+ end
86
+ end
data/lib/raygun/client.rb CHANGED
@@ -1,302 +1,305 @@
1
- module Raygun
2
- # client for the Raygun REST APIv1
3
- # as per http://raygun.io/raygun-providers/rest-json-api?v=1
4
- class Client
5
-
6
- ENV_IP_ADDRESS_KEYS = %w(action_dispatch.remote_ip raygun.remote_ip REMOTE_ADDR)
7
- NO_API_KEY_MESSAGE = "[RAYGUN] Just a note, you've got no API Key configured, which means we can't report exceptions. Specify your Raygun API key using Raygun#setup (find yours at https://app.raygun.io)"
8
-
9
- include HTTParty
10
-
11
- def initialize
12
- @api_key = require_api_key
13
- @headers = {
14
- "X-ApiKey" => @api_key
15
- }
16
-
17
- enable_http_proxy if Raygun.configuration.proxy_settings[:address]
18
- self.class.base_uri Raygun.configuration.api_url
19
- self.class.default_timeout(Raygun.configuration.error_report_send_timeout)
20
- end
21
-
22
- def require_api_key
23
- Raygun.configuration.api_key || print_api_key_warning
24
- end
25
-
26
- def track_exception(exception_instance, env = {}, user = nil)
27
- create_entry(build_payload_hash(exception_instance, env, user))
28
- end
29
-
30
- private
31
-
32
- def enable_http_proxy
33
- self.class.http_proxy(Raygun.configuration.proxy_settings[:address],
34
- Raygun.configuration.proxy_settings[:port] || "80",
35
- Raygun.configuration.proxy_settings[:username],
36
- Raygun.configuration.proxy_settings[:password])
37
- end
38
-
39
- def client_details
40
- {
41
- name: Raygun::CLIENT_NAME,
42
- version: Raygun::VERSION,
43
- clientUrl: Raygun::CLIENT_URL
44
- }
45
- end
46
-
47
- def error_details(exception)
48
- details = {
49
- className: exception.class.to_s,
50
- message: exception.message.to_s.encode('UTF-16', :undef => :replace, :invalid => :replace).encode('UTF-8'),
51
- stackTrace: (exception.backtrace || []).map { |line| stack_trace_for(line) },
52
- }
53
-
54
- details.update(innerError: error_details(exception.cause)) if exception.respond_to?(:cause) && exception.cause
55
-
56
- details
57
- end
58
-
59
- def stack_trace_for(line)
60
- # see http://www.ruby-doc.org/core-2.0/Exception.html#method-i-backtrace
61
- file_name, line_number, method = line.split(":")
62
- {
63
- lineNumber: line_number,
64
- fileName: file_name,
65
- methodName: method ? method.gsub(/^in `(.*?)'$/, "\\1") : "(none)"
66
- }
67
- end
68
-
69
- def hostname
70
- Socket.gethostname
71
- end
72
-
73
- def version
74
- Raygun.configuration.version
75
- end
76
-
77
- def user_information(env)
78
- env["raygun.affected_user"]
79
- end
80
-
81
- def affected_user_present?(env)
82
- !!env["raygun.affected_user"]
83
- end
84
-
85
- def rack_env
86
- ENV["RACK_ENV"]
87
- end
88
-
89
- def rails_env
90
- ENV["RAILS_ENV"]
91
- end
92
-
93
- def request_information(env)
94
- Raygun.log('retrieving request information')
95
-
96
- return {} if env.nil? || env.empty?
97
- {
98
- hostName: env["SERVER_NAME"],
99
- url: env["PATH_INFO"],
100
- httpMethod: env["REQUEST_METHOD"],
101
- iPAddress: "#{ip_address_from(env)}",
102
- queryString: Rack::Utils.parse_nested_query(env["QUERY_STRING"]),
103
- headers: headers(env),
104
- form: form_params(env),
105
- rawData: raw_data(env)
106
- }
107
- end
108
-
109
- def headers(rack_env)
110
- rack_env.select { |k, v| k.to_s.start_with?("HTTP_") }.inject({}) do |hsh, (k, v)|
111
- hsh[normalize_raygun_header_key(k)] = v
112
- hsh
113
- end
114
- end
115
-
116
- def normalize_raygun_header_key(key)
117
- key.sub(/^HTTP_/, '')
118
- .sub(/_/, ' ')
119
- .split.map(&:capitalize).join(' ')
120
- .sub(/ /, '-')
121
- end
122
-
123
- def form_params(env)
124
- Raygun.log('retrieving form params')
125
-
126
- params = action_dispatch_params(env) || rack_params(env) || {}
127
- filter_params_with_blacklist(params, env["action_dispatch.parameter_filter"])
128
- end
129
-
130
- def action_dispatch_params(env)
131
- env["action_dispatch.request.parameters"]
132
- end
133
-
134
- def rack_params(env)
135
- request = Rack::Request.new(env)
136
- request.params if env["rack.input"]
137
- end
138
-
139
- def raw_data(rack_env)
140
- Raygun.log('retrieving raw data')
141
- request = Rack::Request.new(rack_env)
142
-
143
- return unless Raygun.configuration.record_raw_data
144
- return if request.get?
145
- Raygun.log('passed raw_data checks')
146
-
147
- input = rack_env['rack.input']
148
-
149
- if input && !request.form_data?
150
- input.rewind
151
-
152
- body = input.read(4096) || ''
153
- input.rewind
154
-
155
- body
156
- else
157
- {}
158
- end
159
- end
160
-
161
- def filter_custom_data(env)
162
- params = env.delete(:custom_data) || {}
163
- filter_params_with_blacklist(params, env["action_dispatch.parameter_filter"])
164
- end
165
-
166
- # see http://raygun.io/raygun-providers/rest-json-api?v=1
167
- def build_payload_hash(exception_instance, env = {}, user = nil)
168
- Raygun.log('building payload hash')
169
- custom_data = filter_custom_data(env) || {}
170
- exception_custom_data = if exception_instance.respond_to?(:raygun_custom_data)
171
- exception_instance.raygun_custom_data
172
- else
173
- {}
174
- end
175
-
176
- tags = env.delete(:tags) || []
177
-
178
- if rails_env
179
- tags << rails_env
180
- else
181
- tags << rack_env
182
- end
183
-
184
- configuration_tags = []
185
- if Raygun.configuration.tags.is_a?(Proc)
186
- configuration_tags = Raygun.configuration.tags.call(exception_instance, env)
187
- else
188
- configuration_tags = Raygun.configuration.tags
189
- end
190
-
191
- Raygun.log('set tags')
192
-
193
- grouping_key = env.delete(:grouping_key)
194
-
195
- configuration_custom_data = Raygun.configuration.custom_data
196
- configured_custom_data = if configuration_custom_data.is_a?(Proc)
197
- configuration_custom_data.call(exception_instance, env)
198
- else
199
- configuration_custom_data
200
- end
201
-
202
- Raygun.log('set custom data')
203
-
204
- error_details = {
205
- machineName: hostname,
206
- version: version,
207
- client: client_details,
208
- error: error_details(exception_instance),
209
- userCustomData: exception_custom_data.merge(custom_data).merge(configured_custom_data),
210
- tags: configuration_tags.concat(tags).compact.uniq,
211
- request: request_information(env),
212
- environment: {
213
- utcOffset: Time.now.utc_offset / 3600
214
- }
215
- }
216
- store = ::Raygun::Breadcrumbs::Store
217
- error_details[:breadcrumbs] = store.stored.map(&:build_payload) if store.any?
218
-
219
- Raygun.log('set details and breadcrumbs')
220
-
221
- error_details.merge!(groupingKey: grouping_key) if grouping_key
222
-
223
- user_details = if affected_user_present?(env)
224
- user_information(env)
225
- elsif user != nil
226
- AffectedUser.information_hash(user)
227
- end
228
- error_details.merge!(user: user_details) unless user_details == nil
229
-
230
- Raygun.log('set user details')
231
-
232
- if Raygun.configuration.filter_payload_with_whitelist
233
- Raygun.log('filtering payload with whitelist')
234
- error_details = filter_payload_with_whitelist(error_details)
235
- end
236
-
237
- {
238
- occurredOn: Time.now.utc.iso8601,
239
- details: error_details
240
- }
241
- end
242
-
243
- def create_entry(payload_hash)
244
- Raygun.log('sending payload to api')
245
-
246
- self.class.post(
247
- "/entries",
248
- verify_peer: true,
249
- verify: true,
250
- headers: @headers,
251
- body: JSON.generate(payload_hash),
252
- )
253
- end
254
-
255
- def filter_params_with_blacklist(params_hash = {}, extra_filter_keys = nil)
256
- filter_parameters = Raygun.configuration.filter_parameters
257
-
258
- if filter_parameters.is_a? Proc
259
- filter_parameters.call(params_hash)
260
- else
261
- filter_keys = (Array(extra_filter_keys) + filter_parameters).map(&:to_s)
262
-
263
- filter_params_with_array(params_hash, filter_keys)
264
- end
265
- end
266
-
267
- def filter_payload_with_whitelist(payload_hash)
268
- shape = Raygun.configuration.whitelist_payload_shape
269
-
270
- if shape.is_a? Proc
271
- shape.call(payload_hash)
272
- else
273
- # Always keep the client hash, so force it to true here
274
- Services::ApplyWhitelistFilterToPayload.new.call(shape.merge(client: true), payload_hash)
275
- end
276
- end
277
-
278
- def filter_params_with_array(params_hash, filter_keys)
279
- # Recursive filtering of (nested) hashes
280
- (params_hash || {}).inject({}) do |result, (k, v)|
281
- result[k] = case v
282
- when Hash
283
- filter_params_with_array(v, filter_keys)
284
- else
285
- filter_keys.any? { |fk| /#{fk}/i === k.to_s } ? "[FILTERED]" : v
286
- end
287
- result
288
- end
289
- end
290
-
291
- def ip_address_from(env_hash)
292
- ENV_IP_ADDRESS_KEYS.each do |key_to_try|
293
- return env_hash[key_to_try] unless env_hash[key_to_try].nil? || env_hash[key_to_try] == ""
294
- end
295
- "(Not Available)"
296
- end
297
-
298
- def print_api_key_warning
299
- $stderr.puts(NO_API_KEY_MESSAGE)
300
- end
301
- end
302
- end
1
+ module Raygun
2
+ # client for the Raygun REST APIv1
3
+ # as per https://raygun.com/documentation/product-guides/crash-reporting/api/
4
+ class Client
5
+
6
+ ENV_IP_ADDRESS_KEYS = %w(action_dispatch.remote_ip raygun.remote_ip REMOTE_ADDR)
7
+ NO_API_KEY_MESSAGE = "[RAYGUN] Just a note, you've got no API Key configured, which means we can't report exceptions. Specify your Raygun API key using Raygun#setup (find yours at https://app.raygun.com)"
8
+ MAX_BREADCRUMBS_SIZE = 100_000
9
+
10
+ include HTTParty
11
+
12
+ def initialize
13
+ @api_key = require_api_key
14
+ @headers = {
15
+ "X-ApiKey" => @api_key
16
+ }
17
+
18
+ enable_http_proxy if Raygun.configuration.proxy_settings[:address]
19
+ self.class.base_uri Raygun.configuration.api_url
20
+ self.class.default_timeout(Raygun.configuration.error_report_send_timeout)
21
+ end
22
+
23
+ def require_api_key
24
+ Raygun.configuration.api_key || print_api_key_warning
25
+ end
26
+
27
+ def track_exception(exception_instance, env = {}, user = nil)
28
+ create_entry(build_payload_hash(exception_instance, env, user))
29
+ end
30
+
31
+ private
32
+
33
+ def enable_http_proxy
34
+ self.class.http_proxy(Raygun.configuration.proxy_settings[:address],
35
+ Raygun.configuration.proxy_settings[:port] || "80",
36
+ Raygun.configuration.proxy_settings[:username],
37
+ Raygun.configuration.proxy_settings[:password])
38
+ end
39
+
40
+ def client_details
41
+ {
42
+ name: Raygun::CLIENT_NAME,
43
+ version: Raygun::VERSION,
44
+ clientUrl: Raygun::CLIENT_URL
45
+ }
46
+ end
47
+
48
+ def error_details(exception)
49
+ details = {
50
+ className: exception.class.to_s,
51
+ message: exception.message.to_s.encode('UTF-16', :undef => :replace, :invalid => :replace).encode('UTF-8'),
52
+ stackTrace: (exception.backtrace || []).map { |line| stack_trace_for(line) },
53
+ }
54
+
55
+ details.update(innerError: error_details(exception.cause)) if exception.respond_to?(:cause) && exception.cause
56
+
57
+ details
58
+ end
59
+
60
+ def stack_trace_for(line)
61
+ # see http://www.ruby-doc.org/core-2.0/Exception.html#method-i-backtrace
62
+ file_name, line_number, method = line.split(":")
63
+ {
64
+ lineNumber: line_number,
65
+ fileName: file_name,
66
+ methodName: method ? method.gsub(/^in `(.*?)'$/, "\\1") : "(none)"
67
+ }
68
+ end
69
+
70
+ def hostname
71
+ Socket.gethostname
72
+ end
73
+
74
+ def version
75
+ Raygun.configuration.version
76
+ end
77
+
78
+ def user_information(env)
79
+ env["raygun.affected_user"]
80
+ end
81
+
82
+ def affected_user_present?(env)
83
+ !!env["raygun.affected_user"]
84
+ end
85
+
86
+ def rack_env
87
+ ENV["RACK_ENV"]
88
+ end
89
+
90
+ def rails_env
91
+ ENV["RAILS_ENV"]
92
+ end
93
+
94
+ def request_information(env)
95
+ Raygun.log('retrieving request information')
96
+
97
+ return {} if env.nil? || env.empty?
98
+ {
99
+ hostName: env["SERVER_NAME"],
100
+ url: env["PATH_INFO"],
101
+ httpMethod: env["REQUEST_METHOD"],
102
+ iPAddress: "#{ip_address_from(env)}",
103
+ queryString: Rack::Utils.parse_nested_query(env["QUERY_STRING"]),
104
+ headers: headers(env),
105
+ form: form_params(env),
106
+ rawData: raw_data(env)
107
+ }
108
+ end
109
+
110
+ def headers(rack_env)
111
+ rack_env.select { |k, v| k.to_s.start_with?("HTTP_") }.inject({}) do |hsh, (k, v)|
112
+ hsh[normalize_raygun_header_key(k)] = v
113
+ hsh
114
+ end
115
+ end
116
+
117
+ def normalize_raygun_header_key(key)
118
+ key.sub(/^HTTP_/, '')
119
+ .sub(/_/, ' ')
120
+ .split.map(&:capitalize).join(' ')
121
+ .sub(/ /, '-')
122
+ end
123
+
124
+ def form_params(env)
125
+ Raygun.log('retrieving form params')
126
+
127
+ params = action_dispatch_params(env) || rack_params(env) || {}
128
+ filter_params_with_blacklist(params, env["action_dispatch.parameter_filter"])
129
+ end
130
+
131
+ def action_dispatch_params(env)
132
+ env["action_dispatch.request.parameters"]
133
+ end
134
+
135
+ def rack_params(env)
136
+ request = Rack::Request.new(env)
137
+ request.params if env["rack.input"]
138
+ end
139
+
140
+ def raw_data(rack_env)
141
+ Raygun.log('retrieving raw data')
142
+ request = Rack::Request.new(rack_env)
143
+
144
+ return unless Raygun.configuration.record_raw_data
145
+ return if request.get?
146
+ Raygun.log('passed raw_data checks')
147
+
148
+ input = rack_env['rack.input']
149
+
150
+ if input && !request.form_data?
151
+ input.rewind
152
+
153
+ body = input.read(4096) || ''
154
+ input.rewind
155
+
156
+ body
157
+ else
158
+ {}
159
+ end
160
+ end
161
+
162
+ def filter_custom_data(env)
163
+ params = env.delete(:custom_data) || {}
164
+ filter_params_with_blacklist(params, env["action_dispatch.parameter_filter"])
165
+ end
166
+
167
+ # see https://raygun.com/documentation/product-guides/crash-reporting/api/
168
+ def build_payload_hash(exception_instance, env = {}, user = nil)
169
+ Raygun.log('building payload hash')
170
+ custom_data = filter_custom_data(env) || {}
171
+ exception_custom_data = if exception_instance.respond_to?(:raygun_custom_data)
172
+ exception_instance.raygun_custom_data
173
+ else
174
+ {}
175
+ end
176
+
177
+ tags = env.delete(:tags) || []
178
+
179
+ if rails_env
180
+ tags << rails_env
181
+ else
182
+ tags << rack_env
183
+ end
184
+
185
+ configuration_tags = []
186
+ if Raygun.configuration.tags.is_a?(Proc)
187
+ configuration_tags = Raygun.configuration.tags.call(exception_instance, env)
188
+ else
189
+ configuration_tags = Raygun.configuration.tags
190
+ end
191
+
192
+ Raygun.log('set tags')
193
+
194
+ grouping_key = env.delete(:grouping_key)
195
+ correlation_id = env.delete(:correlation_id)
196
+
197
+ configuration_custom_data = Raygun.configuration.custom_data
198
+ configured_custom_data = if configuration_custom_data.is_a?(Proc)
199
+ configuration_custom_data.call(exception_instance, env)
200
+ else
201
+ configuration_custom_data
202
+ end
203
+
204
+ Raygun.log('set custom data')
205
+
206
+ error_details = {
207
+ machineName: hostname,
208
+ version: version,
209
+ client: client_details,
210
+ error: error_details(exception_instance),
211
+ userCustomData: exception_custom_data.merge(custom_data).merge(configured_custom_data),
212
+ tags: configuration_tags.concat(tags).compact.uniq,
213
+ request: request_information(env),
214
+ environment: {
215
+ utcOffset: Time.now.utc_offset / 3600
216
+ }
217
+ }
218
+ store = ::Raygun::Breadcrumbs::Store
219
+ error_details[:breadcrumbs] = store.take_until_size(MAX_BREADCRUMBS_SIZE).map(&:build_payload) if store.any?
220
+
221
+ Raygun.log('set details and breadcrumbs')
222
+
223
+ error_details.merge!(groupingKey: grouping_key) if grouping_key
224
+ error_details.merge!(correlationId: correlation_id) if correlation_id
225
+
226
+ user_details = if affected_user_present?(env)
227
+ user_information(env)
228
+ elsif user != nil
229
+ AffectedUser.information_hash(user)
230
+ end
231
+ error_details.merge!(user: user_details) unless user_details == nil
232
+
233
+ Raygun.log('set user details')
234
+
235
+ if Raygun.configuration.filter_payload_with_whitelist
236
+ Raygun.log('filtering payload with whitelist')
237
+ error_details = filter_payload_with_whitelist(error_details)
238
+ end
239
+
240
+ {
241
+ occurredOn: Time.now.utc.iso8601,
242
+ details: error_details
243
+ }
244
+ end
245
+
246
+ def create_entry(payload_hash)
247
+ Raygun.log('sending payload to api')
248
+
249
+ self.class.post(
250
+ "/entries",
251
+ verify_peer: true,
252
+ verify: true,
253
+ headers: @headers,
254
+ body: JSON.generate(payload_hash),
255
+ )
256
+ end
257
+
258
+ def filter_params_with_blacklist(params_hash = {}, extra_filter_keys = nil)
259
+ filter_parameters = Raygun.configuration.filter_parameters
260
+
261
+ if filter_parameters.is_a? Proc
262
+ filter_parameters.call(params_hash)
263
+ else
264
+ filter_keys = (Array(extra_filter_keys) + filter_parameters).map(&:to_s)
265
+
266
+ filter_params_with_array(params_hash, filter_keys)
267
+ end
268
+ end
269
+
270
+ def filter_payload_with_whitelist(payload_hash)
271
+ shape = Raygun.configuration.whitelist_payload_shape
272
+
273
+ if shape.is_a? Proc
274
+ shape.call(payload_hash)
275
+ else
276
+ # Always keep the client hash, so force it to true here
277
+ Services::ApplyWhitelistFilterToPayload.new.call(shape.merge(client: true), payload_hash)
278
+ end
279
+ end
280
+
281
+ def filter_params_with_array(params_hash, filter_keys)
282
+ # Recursive filtering of (nested) hashes
283
+ (params_hash || {}).inject({}) do |result, (k, v)|
284
+ result[k] = case v
285
+ when Hash
286
+ filter_params_with_array(v, filter_keys)
287
+ else
288
+ filter_keys.any? { |fk| /#{fk}/i === k.to_s } ? "[FILTERED]" : v
289
+ end
290
+ result
291
+ end
292
+ end
293
+
294
+ def ip_address_from(env_hash)
295
+ ENV_IP_ADDRESS_KEYS.each do |key_to_try|
296
+ return env_hash[key_to_try] unless env_hash[key_to_try].nil? || env_hash[key_to_try] == ""
297
+ end
298
+ "(Not Available)"
299
+ end
300
+
301
+ def print_api_key_warning
302
+ $stderr.puts(NO_API_KEY_MESSAGE)
303
+ end
304
+ end
305
+ end