raygun4ruby 3.2.3 → 3.2.4

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