cloudtrapper 0.0.2.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/CHANGELOG +823 -0
  2. data/Gemfile +12 -0
  3. data/Guardfile +6 -0
  4. data/INSTALL +20 -0
  5. data/MIT-LICENSE +22 -0
  6. data/README.md +465 -0
  7. data/README_FOR_HEROKU_ADDON.md +94 -0
  8. data/Rakefile +223 -0
  9. data/SUPPORTED_RAILS_VERSIONS +23 -0
  10. data/TESTING.md +33 -0
  11. data/cloudtrapper.gemspec +35 -0
  12. data/features/metal.feature +18 -0
  13. data/features/rack.feature +56 -0
  14. data/features/rails.feature +211 -0
  15. data/features/rails_with_js_notifier.feature +97 -0
  16. data/features/rake.feature +27 -0
  17. data/features/sinatra.feature +29 -0
  18. data/features/step_definitions/file_steps.rb +10 -0
  19. data/features/step_definitions/metal_steps.rb +23 -0
  20. data/features/step_definitions/rack_steps.rb +23 -0
  21. data/features/step_definitions/rails_application_steps.rb +433 -0
  22. data/features/step_definitions/rake_steps.rb +17 -0
  23. data/features/support/airbrake_shim.rb.template +11 -0
  24. data/features/support/env.rb +18 -0
  25. data/features/support/matchers.rb +35 -0
  26. data/features/support/rails.rb +201 -0
  27. data/features/support/rake/Rakefile +68 -0
  28. data/features/support/terminal.rb +107 -0
  29. data/features/user_informer.feature +63 -0
  30. data/generators/cloudtrapper/airbrake_generator.rb +94 -0
  31. data/generators/cloudtrapper/lib/insert_commands.rb +34 -0
  32. data/generators/cloudtrapper/lib/rake_commands.rb +24 -0
  33. data/generators/cloudtrapper/templates/capistrano_hook.rb +6 -0
  34. data/generators/cloudtrapper/templates/cloudtrapper_tasks.rake +25 -0
  35. data/generators/cloudtrapper/templates/initializer.rb +6 -0
  36. data/install.rb +1 -0
  37. data/lib/cloudtrapper/backtrace.rb +100 -0
  38. data/lib/cloudtrapper/capistrano.rb +44 -0
  39. data/lib/cloudtrapper/configuration.rb +281 -0
  40. data/lib/cloudtrapper/notice.rb +348 -0
  41. data/lib/cloudtrapper/rack.rb +55 -0
  42. data/lib/cloudtrapper/rails/action_controller_catcher.rb +30 -0
  43. data/lib/cloudtrapper/rails/controller_methods.rb +74 -0
  44. data/lib/cloudtrapper/rails/error_lookup.rb +33 -0
  45. data/lib/cloudtrapper/rails/javascript_notifier.rb +48 -0
  46. data/lib/cloudtrapper/rails/middleware/exceptions_catcher.rb +29 -0
  47. data/lib/cloudtrapper/rails.rb +40 -0
  48. data/lib/cloudtrapper/rails3_tasks.rb +85 -0
  49. data/lib/cloudtrapper/railtie.rb +48 -0
  50. data/lib/cloudtrapper/rake_handler.rb +66 -0
  51. data/lib/cloudtrapper/sender.rb +116 -0
  52. data/lib/cloudtrapper/shared_tasks.rb +36 -0
  53. data/lib/cloudtrapper/tasks.rb +83 -0
  54. data/lib/cloudtrapper/user_informer.rb +27 -0
  55. data/lib/cloudtrapper/version.rb +3 -0
  56. data/lib/cloudtrapper.rb +155 -0
  57. data/lib/cloudtrapper_tasks.rb +65 -0
  58. data/lib/rails/generators/cloudtrapper/cloudtrapper_generator.rb +100 -0
  59. data/lib/templates/javascript_notifier.erb +15 -0
  60. data/lib/templates/rescue.erb +91 -0
  61. data/rails/init.rb +1 -0
  62. data/resources/README.md +34 -0
  63. data/resources/ca-bundle.crt +3376 -0
  64. data/script/integration_test.rb +38 -0
  65. data/test/backtrace_test.rb +162 -0
  66. data/test/capistrano_test.rb +34 -0
  67. data/test/catcher_test.rb +333 -0
  68. data/test/cloudtrapper_2_2.xsd +78 -0
  69. data/test/cloudtrapper_tasks_test.rb +170 -0
  70. data/test/configuration_test.rb +221 -0
  71. data/test/helper.rb +263 -0
  72. data/test/javascript_notifier_test.rb +52 -0
  73. data/test/logger_test.rb +73 -0
  74. data/test/notice_test.rb +468 -0
  75. data/test/notifier_test.rb +246 -0
  76. data/test/rack_test.rb +58 -0
  77. data/test/rails_initializer_test.rb +36 -0
  78. data/test/recursion_test.rb +10 -0
  79. data/test/sender_test.rb +261 -0
  80. data/test/user_informer_test.rb +29 -0
  81. metadata +301 -0
@@ -0,0 +1,348 @@
1
+ require 'builder'
2
+ require 'socket'
3
+
4
+ module Cloudtrapper
5
+ class Notice
6
+
7
+ # The exception that caused this notice, if any
8
+ attr_reader :exception
9
+
10
+ # The API key for the project to which this notice should be sent
11
+ attr_reader :api_key
12
+
13
+ # The backtrace from the given exception or hash.
14
+ attr_reader :backtrace
15
+
16
+ # The name of the class of error (such as RuntimeError)
17
+ attr_reader :error_class
18
+
19
+ # The name of the server environment (such as "production")
20
+ attr_reader :environment_name
21
+
22
+ # CGI variables such as HTTP_METHOD
23
+ attr_reader :cgi_data
24
+
25
+ # The message from the exception, or a general description of the error
26
+ attr_reader :error_message
27
+
28
+ # See Configuration#backtrace_filters
29
+ attr_reader :backtrace_filters
30
+
31
+ # See Configuration#params_filters
32
+ attr_reader :params_filters
33
+
34
+ # A hash of parameters from the query string or post body.
35
+ attr_reader :parameters
36
+ alias_method :params, :parameters
37
+
38
+ # The component (if any) which was used in this request (usually the controller)
39
+ attr_reader :component
40
+ alias_method :controller, :component
41
+
42
+ # The action (if any) that was called in this request
43
+ attr_reader :action
44
+
45
+ # A hash of session data from the request
46
+ attr_reader :session_data
47
+
48
+ # The path to the project that caused the error (usually Rails.root)
49
+ attr_reader :project_root
50
+
51
+ # The URL at which the error occurred (if any)
52
+ attr_reader :url
53
+
54
+ # See Configuration#ignore
55
+ attr_reader :ignore
56
+
57
+ # See Configuration#ignore_by_filters
58
+ attr_reader :ignore_by_filters
59
+
60
+ # The name of the notifier library sending this notice, such as "Cloudtrapper Notifier"
61
+ attr_reader :notifier_name
62
+
63
+ # The version number of the notifier library sending this notice, such as "2.1.3"
64
+ attr_reader :notifier_version
65
+
66
+ # A URL for more information about the notifier library sending this notice
67
+ attr_reader :notifier_url
68
+
69
+ # The host name where this error occurred (if any)
70
+ attr_reader :hostname
71
+
72
+ def initialize(args)
73
+ self.args = args
74
+ self.exception = args[:exception]
75
+ self.api_key = args[:api_key]
76
+ self.project_root = args[:project_root]
77
+ self.url = args[:url] || rack_env(:url)
78
+
79
+ self.notifier_name = args[:notifier_name]
80
+ self.notifier_version = args[:notifier_version]
81
+ self.notifier_url = args[:notifier_url]
82
+
83
+ self.ignore = args[:ignore] || []
84
+ self.ignore_by_filters = args[:ignore_by_filters] || []
85
+ self.backtrace_filters = args[:backtrace_filters] || []
86
+ self.params_filters = args[:params_filters] || []
87
+ self.parameters = args[:parameters] ||
88
+ action_dispatch_params ||
89
+ rack_env(:params) ||
90
+ {}
91
+ self.component = args[:component] || args[:controller] || parameters['controller']
92
+ self.action = args[:action] || parameters['action']
93
+
94
+ self.environment_name = args[:environment_name]
95
+ self.cgi_data = args[:cgi_data] || args[:rack_env]
96
+ self.backtrace = Backtrace.parse(exception_attribute(:backtrace, caller), :filters => self.backtrace_filters)
97
+ self.error_class = exception_attribute(:error_class) {|exception| exception.class.name }
98
+ self.error_message = exception_attribute(:error_message, 'Notification') do |exception|
99
+ "#{exception.class.name}: #{exception.message}"
100
+ end
101
+
102
+ self.hostname = local_hostname
103
+
104
+ also_use_rack_params_filters
105
+ find_session_data
106
+ clean_params
107
+ clean_rack_request_data
108
+ end
109
+
110
+ # Converts the given notice to XML
111
+ def to_xml
112
+ builder = Builder::XmlMarkup.new
113
+ builder.instruct!
114
+ xml = builder.notice(:version => Cloudtrapper::API_VERSION) do |notice|
115
+ notice.tag!("api-key", api_key)
116
+ notice.notifier do |notifier|
117
+ notifier.name(notifier_name)
118
+ notifier.version(notifier_version)
119
+ notifier.url(notifier_url)
120
+ end
121
+ notice.error do |error|
122
+ error.tag!('class', error_class)
123
+ error.message(error_message)
124
+ error.backtrace do |backtrace|
125
+ self.backtrace.lines.each do |line|
126
+ backtrace.line(:number => line.number,
127
+ :file => line.file,
128
+ :method => line.method)
129
+ end
130
+ end
131
+ end
132
+ if url ||
133
+ controller ||
134
+ action ||
135
+ !parameters.blank? ||
136
+ !cgi_data.blank? ||
137
+ !session_data.blank?
138
+ notice.request do |request|
139
+ request.url(url)
140
+ request.component(controller)
141
+ request.action(action)
142
+ unless parameters.nil? || parameters.empty?
143
+ request.params do |params|
144
+ xml_vars_for(params, parameters)
145
+ end
146
+ end
147
+ unless session_data.nil? || session_data.empty?
148
+ request.session do |session|
149
+ xml_vars_for(session, session_data)
150
+ end
151
+ end
152
+ unless cgi_data.nil? || cgi_data.empty?
153
+ request.tag!("cgi-data") do |cgi_datum|
154
+ xml_vars_for(cgi_datum, cgi_data)
155
+ end
156
+ end
157
+ end
158
+ end
159
+ notice.tag!("server-environment") do |env|
160
+ env.tag!("project-root", project_root)
161
+ env.tag!("environment-name", environment_name)
162
+ env.tag!("hostname", hostname)
163
+ end
164
+ end
165
+ xml.to_s
166
+ end
167
+
168
+ # Determines if this notice should be ignored
169
+ def ignore?
170
+ ignored_class_names.include?(error_class) ||
171
+ ignore_by_filters.any? {|filter| filter.call(self) }
172
+ end
173
+
174
+ # Allows properties to be accessed using a hash-like syntax
175
+ #
176
+ # @example
177
+ # notice[:error_message]
178
+ # @param [String] method The given key for an attribute
179
+ # @return The attribute value, or self if given +:request+
180
+ def [](method)
181
+ case method
182
+ when :request
183
+ self
184
+ else
185
+ send(method)
186
+ end
187
+ end
188
+
189
+ private
190
+
191
+ attr_writer :exception, :api_key, :backtrace, :error_class, :error_message,
192
+ :backtrace_filters, :parameters, :params_filters,
193
+ :environment_filters, :session_data, :project_root, :url, :ignore,
194
+ :ignore_by_filters, :notifier_name, :notifier_url, :notifier_version,
195
+ :component, :action, :cgi_data, :environment_name, :hostname
196
+
197
+ # Arguments given in the initializer
198
+ attr_accessor :args
199
+
200
+ # Gets a property named +attribute+ of an exception, either from an actual
201
+ # exception or a hash.
202
+ #
203
+ # If an exception is available, #from_exception will be used. Otherwise,
204
+ # a key named +attribute+ will be used from the #args.
205
+ #
206
+ # If no exception or hash key is available, +default+ will be used.
207
+ def exception_attribute(attribute, default = nil, &block)
208
+ (exception && from_exception(attribute, &block)) || args[attribute] || default
209
+ end
210
+
211
+ # Gets a property named +attribute+ from an exception.
212
+ #
213
+ # If a block is given, it will be used when getting the property from an
214
+ # exception. The block should accept and exception and return the value for
215
+ # the property.
216
+ #
217
+ # If no block is given, a method with the same name as +attribute+ will be
218
+ # invoked for the value.
219
+ def from_exception(attribute)
220
+ if block_given?
221
+ yield(exception)
222
+ else
223
+ exception.send(attribute)
224
+ end
225
+ end
226
+
227
+ # Removes non-serializable data from the given attribute.
228
+ # See #clean_unserializable_data
229
+ def clean_unserializable_data_from(attribute)
230
+ self.send(:"#{attribute}=", clean_unserializable_data(send(attribute)))
231
+ end
232
+
233
+ # Removes non-serializable data. Allowed data types are strings, arrays,
234
+ # and hashes. All other types are converted to strings.
235
+ # TODO: move this onto Hash
236
+ def clean_unserializable_data(data, stack = [])
237
+ return "[possible infinite recursion halted]" if stack.any?{|item| item == data.object_id }
238
+
239
+ if data.respond_to?(:to_hash)
240
+ data.to_hash.inject({}) do |result, (key, value)|
241
+ result.merge(key => clean_unserializable_data(value, stack + [data.object_id]))
242
+ end
243
+ elsif data.respond_to?(:to_ary)
244
+ data.to_ary.collect do |value|
245
+ clean_unserializable_data(value, stack + [data.object_id])
246
+ end
247
+ else
248
+ data.to_s
249
+ end
250
+ end
251
+
252
+ # Replaces the contents of params that match params_filters.
253
+ # TODO: extract this to a different class
254
+ def clean_params
255
+ clean_unserializable_data_from(:parameters)
256
+ filter(parameters)
257
+ if cgi_data
258
+ clean_unserializable_data_from(:cgi_data)
259
+ filter(cgi_data)
260
+ end
261
+ if session_data
262
+ clean_unserializable_data_from(:session_data)
263
+ filter(session_data)
264
+ end
265
+ end
266
+
267
+ def clean_rack_request_data
268
+ if cgi_data
269
+ cgi_data.delete("rack.request.form_vars")
270
+ end
271
+ end
272
+
273
+ def filter(hash)
274
+ if params_filters
275
+ hash.each do |key, value|
276
+ if filter_key?(key)
277
+ hash[key] = "[FILTERED]"
278
+ elsif value.respond_to?(:to_hash)
279
+ filter(hash[key])
280
+ end
281
+ end
282
+ end
283
+ end
284
+
285
+ def filter_key?(key)
286
+ params_filters.any? do |filter|
287
+ key.to_s.eql?(filter.to_s)
288
+ end
289
+ end
290
+
291
+ def find_session_data
292
+ self.session_data = args[:session_data] || args[:session] || rack_session || {}
293
+ self.session_data = session_data[:data] if session_data[:data]
294
+ end
295
+
296
+ # Converts the mixed class instances and class names into just names
297
+ # TODO: move this into Configuration or another class
298
+ def ignored_class_names
299
+ ignore.collect do |string_or_class|
300
+ if string_or_class.respond_to?(:name)
301
+ string_or_class.name
302
+ else
303
+ string_or_class
304
+ end
305
+ end
306
+ end
307
+
308
+ def xml_vars_for(builder, hash)
309
+ hash.each do |key, value|
310
+ if value.respond_to?(:to_hash)
311
+ builder.var(:key => key){|b| xml_vars_for(b, value.to_hash) }
312
+ else
313
+ builder.var(value.to_s, :key => key)
314
+ end
315
+ end
316
+ end
317
+
318
+ def rack_env(method)
319
+ rack_request.send(method) if rack_request
320
+ end
321
+
322
+ def rack_request
323
+ @rack_request ||= if args[:rack_env]
324
+ ::Rack::Request.new(args[:rack_env])
325
+ end
326
+ end
327
+
328
+ def action_dispatch_params
329
+ args[:rack_env]['action_dispatch.request.parameters'] if args[:rack_env]
330
+ end
331
+
332
+ def rack_session
333
+ args[:rack_env]['rack.session'] if args[:rack_env]
334
+ end
335
+
336
+ def also_use_rack_params_filters
337
+ if args[:rack_env]
338
+ @params_filters ||= []
339
+ @params_filters += rack_request.env["action_dispatch.parameter_filter"] || []
340
+ end
341
+ end
342
+
343
+ def local_hostname
344
+ Socket.gethostname
345
+ end
346
+
347
+ end
348
+ end
@@ -0,0 +1,55 @@
1
+ module Cloudtrapper
2
+ # Middleware for Rack applications. Any errors raised by the upstream
3
+ # application will be delivered to Cloudtrapper and re-raised.
4
+ #
5
+ # Synopsis:
6
+ #
7
+ # require 'rack'
8
+ # require 'cloudtrapper'
9
+ #
10
+ # Cloudtrapper.configure do |config|
11
+ # config.api_key = 'my_api_key'
12
+ # end
13
+ #
14
+ # app = Rack::Builder.app do
15
+ # run lambda { |env| raise "Rack down" }
16
+ # end
17
+ #
18
+ # use Cloudtrapper::Rack
19
+ # run app
20
+ #
21
+ # Use a standard Cloudtrapper.configure call to configure your api key.
22
+ class Rack
23
+ def initialize(app)
24
+ @app = app
25
+ Cloudtrapper.configuration.logger ||= Logger.new STDOUT
26
+ end
27
+
28
+ def ignored_user_agent?(env)
29
+ true if Cloudtrapper.
30
+ configuration.
31
+ ignore_user_agent.
32
+ flatten.
33
+ any? { |ua| ua === env['HTTP_USER_AGENT'] }
34
+ end
35
+
36
+ def notify_cloudtrapper(exception,env)
37
+ Cloudtrapper.notify_or_ignore(exception,:rack_env => env) unless ignored_user_agent?(env)
38
+ end
39
+
40
+ def call(env)
41
+ begin
42
+ response = @app.call(env)
43
+ rescue Exception => raised
44
+ env['cloudtrapper.error_id'] = notify_cloudtrapper(raised,env)
45
+ raise
46
+ end
47
+
48
+ if env['rack.exception']
49
+ env['cloudtrapper.error_id'] = notify_cloudtrapper(env['rack.exception'],env)
50
+ end
51
+
52
+ response
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,30 @@
1
+ module Cloudtrapper
2
+ module Rails
3
+ module ActionControllerCatcher
4
+
5
+ # Sets up an alias chain to catch exceptions when Rails does
6
+ def self.included(base) #:nodoc:
7
+ base.send(:alias_method, :rescue_action_in_public_without_cloudtrapper, :rescue_action_in_public)
8
+ base.send(:alias_method, :rescue_action_in_public, :rescue_action_in_public_with_cloudtrapper)
9
+ end
10
+
11
+ private
12
+
13
+ # Overrides the rescue_action method in ActionController::Base, but does not inhibit
14
+ # any custom processing that is defined with Rails 2's exception helpers.
15
+ def rescue_action_in_public_with_cloudtrapper(exception)
16
+ unless cloudtrapper_ignore_user_agent?
17
+ error_id = Cloudtrapper.notify_or_ignore(exception, cloudtrapper_request_data)
18
+ request.env['cloudtrapper.error_id'] = error_id
19
+ end
20
+ rescue_action_in_public_without_cloudtrapper(exception)
21
+ end
22
+
23
+ def cloudtrapper_ignore_user_agent? #:nodoc:
24
+ # Rails 1.2.6 doesn't have request.user_agent, so check for it here
25
+ user_agent = request.respond_to?(:user_agent) ? request.user_agent : request.env["HTTP_USER_AGENT"]
26
+ Cloudtrapper.configuration.ignore_user_agent.flatten.any? { |ua| ua === user_agent }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,74 @@
1
+ module Cloudtrapper
2
+ module Rails
3
+ module ControllerMethods
4
+
5
+ def cloudtrapper_request_data
6
+ { :parameters => cloudtrapper_filter_if_filtering(params.to_hash),
7
+ :session_data => cloudtrapper_filter_if_filtering(cloudtrapper_session_data),
8
+ :controller => params[:controller],
9
+ :action => params[:action],
10
+ :url => cloudtrapper_request_url,
11
+ :cgi_data => cloudtrapper_filter_if_filtering(request.env) }
12
+ end
13
+
14
+ private
15
+
16
+ # This method should be used for sending manual notifications while you are still
17
+ # inside the controller. Otherwise it works like Cloudtrapper.notify.
18
+ def notify_cloudtrapper(hash_or_exception)
19
+ unless cloudtrapper_local_request?
20
+ Cloudtrapper.notify(hash_or_exception, cloudtrapper_request_data)
21
+ end
22
+ end
23
+
24
+ def cloudtrapper_local_request?
25
+ if defined?(::Rails.application.config)
26
+ ::Rails.application.config.consider_all_requests_local || (request.local? && (!request.env["HTTP_X_FORWARDED_FOR"]))
27
+ else
28
+ consider_all_requests_local || (local_request? && (!request.env["HTTP_X_FORWARDED_FOR"]))
29
+ end
30
+ end
31
+
32
+ def cloudtrapper_ignore_user_agent? #:nodoc:
33
+ # Rails 1.2.6 doesn't have request.user_agent, so check for it here
34
+ user_agent = request.respond_to?(:user_agent) ? request.user_agent : request.env["HTTP_USER_AGENT"]
35
+ Cloudtrapper.configuration.ignore_user_agent.flatten.any? { |ua| ua === user_agent }
36
+ end
37
+
38
+
39
+ def cloudtrapper_filter_if_filtering(hash)
40
+ return hash if ! hash.is_a?(Hash)
41
+
42
+
43
+ if respond_to?(:filter_parameters) # Rails 2
44
+ filter_parameters(hash)
45
+ elsif defined?(ActionDispatch::Http::ParameterFilter) # Rails 3
46
+ ActionDispatch::Http::ParameterFilter.new(::Rails.application.config.filter_parameters).filter(hash)
47
+ else
48
+ hash
49
+ end rescue hash
50
+
51
+ end
52
+
53
+ def cloudtrapper_session_data
54
+ if session.respond_to?(:to_hash)
55
+ session.to_hash
56
+ else
57
+ session.data
58
+ end
59
+ end
60
+
61
+ def cloudtrapper_request_url
62
+ url = "#{request.protocol}#{request.host}"
63
+
64
+ unless [80, 443].include?(request.port)
65
+ url << ":#{request.port}"
66
+ end
67
+
68
+ url << request.fullpath
69
+ url
70
+ end
71
+ end
72
+ end
73
+ end
74
+
@@ -0,0 +1,33 @@
1
+ module Cloudtrapper
2
+ module Rails
3
+ module ErrorLookup
4
+
5
+ # Sets up an alias chain to catch exceptions when Rails does
6
+ def self.included(base) #:nodoc:
7
+ base.send(:alias_method, :rescue_action_locally_without_cloudtrapper, :rescue_action_locally)
8
+ base.send(:alias_method, :rescue_action_locally, :rescue_action_locally_with_cloudtrapper)
9
+ end
10
+
11
+ private
12
+
13
+ def rescue_action_locally_with_cloudtrapper(exception)
14
+ result = rescue_action_locally_without_cloudtrapper(exception)
15
+
16
+ if Cloudtrapper.configuration.development_lookup
17
+ path = File.join(File.dirname(__FILE__), '..', '..', 'templates', 'rescue.erb')
18
+ notice = Cloudtrapper.build_lookup_hash_for(exception, cloudtrapper_request_data)
19
+
20
+ result << @template.render(
21
+ :file => path,
22
+ :use_full_path => false,
23
+ :locals => { :host => Cloudtrapper.configuration.host,
24
+ :api_key => Cloudtrapper.configuration.api_key,
25
+ :notice => notice })
26
+ end
27
+
28
+ result
29
+ end
30
+ end
31
+ end
32
+ end
33
+
@@ -0,0 +1,48 @@
1
+ module Cloudtrapper
2
+ module Rails
3
+ module JavascriptNotifier
4
+ def self.included(base) #:nodoc:
5
+ base.send :helper_method, :cloudtrapper_javascript_notifier
6
+ end
7
+
8
+ private
9
+
10
+ def cloudtrapper_javascript_notifier
11
+ return unless Cloudtrapper.configuration.public?
12
+
13
+ path = File.join File.dirname(__FILE__), '..', '..', 'templates', 'javascript_notifier.erb'
14
+ host = Cloudtrapper.configuration.host.dup
15
+ port = Cloudtrapper.configuration.port
16
+ host << ":#{port}" unless [80, 443].include?(port)
17
+
18
+ options = {
19
+ :file => path,
20
+ :layout => false,
21
+ :use_full_path => false,
22
+ :locals => {
23
+ :host => host,
24
+ :api_key => Cloudtrapper.configuration.js_api_key,
25
+ :environment => Cloudtrapper.configuration.environment_name,
26
+ :action_name => action_name,
27
+ :controller_name => controller_name,
28
+ :url => request.url
29
+ }
30
+ }
31
+
32
+ res = if @template
33
+ @template.render(options)
34
+ else
35
+ render_to_string(options)
36
+ end
37
+
38
+ if res.respond_to?(:html_safe)
39
+ res.html_safe
40
+ else
41
+ res
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,29 @@
1
+ module Cloudtrapper
2
+ module Rails
3
+ module Middleware
4
+ module ExceptionsCatcher
5
+ def self.included(base)
6
+ base.send(:alias_method_chain,:render_exception,:cloudtrapper)
7
+ end
8
+
9
+ def skip_user_agent?(env)
10
+ user_agent = env["HTTP_USER_AGENT"]
11
+ ::Cloudtrapper.configuration.ignore_user_agent.flatten.any? { |ua| ua === user_agent }
12
+ rescue
13
+ false
14
+ end
15
+
16
+ def render_exception_with_cloudtrapper(env,exception)
17
+ controller = env['action_controller.instance']
18
+ env['cloudtrapper.error_id'] = Cloudtrapper.
19
+ notify_or_ignore(exception,
20
+ controller.try(:cloudtrapper_request_data) || :rack_env => env) unless skip_user_agent?(env)
21
+ if defined?(controller.rescue_action_in_public_without_cloudtrapper)
22
+ controller.rescue_action_in_public_without_cloudtrapper(exception)
23
+ end
24
+ render_exception_without_cloudtrapper(env,exception)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,40 @@
1
+ require 'cloudtrapper'
2
+ require 'cloudtrapper/rails/controller_methods'
3
+ require 'cloudtrapper/rails/action_controller_catcher'
4
+ require 'cloudtrapper/rails/error_lookup'
5
+ require 'cloudtrapper/rails/javascript_notifier'
6
+
7
+ module Cloudtrapper
8
+ module Rails
9
+ def self.initialize
10
+ if defined?(ActionController::Base)
11
+ ActionController::Base.send(:include, Cloudtrapper::Rails::ActionControllerCatcher)
12
+ ActionController::Base.send(:include, Cloudtrapper::Rails::ErrorLookup)
13
+ ActionController::Base.send(:include, Cloudtrapper::Rails::ControllerMethods)
14
+ ActionController::Base.send(:include, Cloudtrapper::Rails::JavascriptNotifier)
15
+ end
16
+
17
+ rails_logger = if defined?(::Rails.logger)
18
+ ::Rails.logger
19
+ elsif defined?(RAILS_DEFAULT_LOGGER)
20
+ RAILS_DEFAULT_LOGGER
21
+ end
22
+
23
+ if defined?(::Rails.configuration) && ::Rails.configuration.respond_to?(:middleware)
24
+ ::Rails.configuration.middleware.insert_after 'ActionController::Failsafe',
25
+ Cloudtrapper::Rack
26
+ ::Rails.configuration.middleware.insert_after 'Rack::Lock',
27
+ Cloudtrapper::UserInformer
28
+ end
29
+
30
+ Cloudtrapper.configure(true) do |config|
31
+ config.logger = rails_logger
32
+ config.environment_name = defined?(::Rails.env) && ::Rails.env || defined?(RAILS_ENV) && RAILS_ENV
33
+ config.project_root = defined?(::Rails.root) && ::Rails.root || defined?(RAILS_ROOT) && RAILS_ROOT
34
+ config.framework = defined?(::Rails.version) && "Rails: #{::Rails.version}" || defined?(::Rails::VERSION::STRING) && "Rails: #{::Rails::VERSION::STRING}"
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ Cloudtrapper::Rails.initialize