raygun4ruby 3.1.1 → 3.2.4

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 (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