projectlocker_pulse 0.2.1

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