projectlocker_pulse 0.2.1

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