raygun4ruby 3.2.3 → 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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +18 -18
  3. data/.rspec +1 -1
  4. data/.travis.yml +20 -20
  5. data/CHANGELOG.md +127 -124
  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 -179
  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 -34
  16. data/lib/raygun/breadcrumbs/store.rb +86 -86
  17. data/lib/raygun/client.rb +305 -305
  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 -70
  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 -17
  35. data/spec/dummy/Gemfile +47 -47
  36. data/spec/dummy/README.rdoc +28 -28
  37. data/spec/dummy/Rakefile +6 -6
  38. data/spec/dummy/app/assets/config/manifest.js +2 -2
  39. data/spec/dummy/app/assets/javascripts/application.js +13 -13
  40. data/spec/dummy/app/assets/stylesheets/application.css +15 -15
  41. data/spec/dummy/app/controllers/application_controller.rb +5 -5
  42. data/spec/dummy/app/controllers/home_controller.rb +4 -4
  43. data/spec/dummy/app/helpers/application_helper.rb +2 -2
  44. data/spec/dummy/app/views/home/index.html.erb +3 -3
  45. data/spec/dummy/app/views/home/index.json.erb +1 -1
  46. data/spec/dummy/app/views/layouts/application.html.erb +14 -14
  47. data/spec/dummy/bin/bundle +3 -3
  48. data/spec/dummy/bin/rails +9 -9
  49. data/spec/dummy/bin/rake +9 -9
  50. data/spec/dummy/bin/setup +29 -29
  51. data/spec/dummy/bin/spring +17 -17
  52. data/spec/dummy/config.ru +4 -4
  53. data/spec/dummy/config/application.rb +26 -26
  54. data/spec/dummy/config/boot.rb +3 -3
  55. data/spec/dummy/config/database.yml +25 -25
  56. data/spec/dummy/config/environment.rb +5 -5
  57. data/spec/dummy/config/environments/development.rb +41 -41
  58. data/spec/dummy/config/environments/production.rb +79 -79
  59. data/spec/dummy/config/environments/test.rb +42 -42
  60. data/spec/dummy/config/initializers/assets.rb +11 -11
  61. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -7
  62. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -3
  63. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -4
  64. data/spec/dummy/config/initializers/inflections.rb +16 -16
  65. data/spec/dummy/config/initializers/mime_types.rb +4 -4
  66. data/spec/dummy/config/initializers/session_store.rb +3 -3
  67. data/spec/dummy/config/initializers/to_time_preserves_timezone.rb +10 -10
  68. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -14
  69. data/spec/dummy/config/locales/en.yml +23 -23
  70. data/spec/dummy/config/routes.rb +58 -58
  71. data/spec/dummy/config/secrets.yml +22 -22
  72. data/spec/dummy/db/seeds.rb +7 -7
  73. data/spec/dummy/public/404.html +67 -67
  74. data/spec/dummy/public/422.html +67 -67
  75. data/spec/dummy/public/500.html +66 -66
  76. data/spec/dummy/public/robots.txt +5 -5
  77. data/spec/dummy/test/test_helper.rb +10 -10
  78. data/spec/features/javascript_spec.rb +48 -48
  79. data/spec/rails_helper.rb +4 -4
  80. data/spec/raygun/breadcrumbs/breadcrumb_spec.rb +171 -171
  81. data/spec/raygun/breadcrumbs/store_spec.rb +170 -170
  82. data/spec/raygun/raygun_spec.rb +47 -47
  83. data/spec/services/apply_whitelist_filter_to_payload_spec.rb +251 -251
  84. data/spec/spec_helper.rb +24 -24
  85. data/spec/support/fake_logger.rb +17 -17
  86. data/test/integration/client_test.rb +19 -19
  87. data/test/test_helper.rb +72 -72
  88. data/test/unit/affected_user_test.rb +136 -136
  89. data/test/unit/client_test.rb +792 -792
  90. data/test/unit/configuration_test.rb +206 -206
  91. data/test/unit/raygun_test.rb +25 -25
  92. data/test/unit/resque_failure_test.rb +24 -24
  93. data/test/unit/sidekiq_failure_test.rb +32 -32
  94. metadata +7 -7
@@ -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,34 +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
-
29
- def size
30
- return message.length + 100
31
- end
32
- end
33
- end
34
- 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,86 +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
- 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
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,305 +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
- 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 http://raygun.io/raygun-providers/rest-json-api?v=1
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
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