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.
- data/CHANGELOG +26 -0
- data/Gemfile +3 -0
- data/Guardfile +6 -0
- data/INSTALL +20 -0
- data/MIT-LICENSE +23 -0
- data/README.md +439 -0
- data/README_FOR_HEROKU_ADDON.md +89 -0
- data/Rakefile +223 -0
- data/SUPPORTED_RAILS_VERSIONS +38 -0
- data/TESTING.md +41 -0
- data/features/metal.feature +18 -0
- data/features/rack.feature +60 -0
- data/features/rails.feature +272 -0
- data/features/rails_with_js_notifier.feature +97 -0
- data/features/rake.feature +27 -0
- data/features/sinatra.feature +29 -0
- data/features/step_definitions/file_steps.rb +10 -0
- data/features/step_definitions/metal_steps.rb +23 -0
- data/features/step_definitions/rack_steps.rb +23 -0
- data/features/step_definitions/rails_application_steps.rb +478 -0
- data/features/step_definitions/rake_steps.rb +17 -0
- data/features/support/env.rb +18 -0
- data/features/support/matchers.rb +35 -0
- data/features/support/projectlocker_pulse_shim.rb.template +16 -0
- data/features/support/rails.rb +201 -0
- data/features/support/rake/Rakefile +68 -0
- data/features/support/terminal.rb +107 -0
- data/features/user_informer.feature +63 -0
- data/generators/pulse/lib/insert_commands.rb +34 -0
- data/generators/pulse/lib/rake_commands.rb +24 -0
- data/generators/pulse/pulse_generator.rb +94 -0
- data/generators/pulse/templates/capistrano_hook.rb +6 -0
- data/generators/pulse/templates/initializer.rb +6 -0
- data/generators/pulse/templates/pulse_tasks.rake +25 -0
- data/install.rb +1 -0
- data/lib/projectlocker_pulse.rb +159 -0
- data/lib/pulse/backtrace.rb +108 -0
- data/lib/pulse/capistrano.rb +43 -0
- data/lib/pulse/configuration.rb +305 -0
- data/lib/pulse/notice.rb +390 -0
- data/lib/pulse/rack.rb +54 -0
- data/lib/pulse/rails/action_controller_catcher.rb +30 -0
- data/lib/pulse/rails/controller_methods.rb +85 -0
- data/lib/pulse/rails/error_lookup.rb +33 -0
- data/lib/pulse/rails/javascript_notifier.rb +47 -0
- data/lib/pulse/rails/middleware/exceptions_catcher.rb +33 -0
- data/lib/pulse/rails.rb +40 -0
- data/lib/pulse/rails3_tasks.rb +99 -0
- data/lib/pulse/railtie.rb +49 -0
- data/lib/pulse/rake_handler.rb +65 -0
- data/lib/pulse/sender.rb +128 -0
- data/lib/pulse/shared_tasks.rb +47 -0
- data/lib/pulse/tasks.rb +83 -0
- data/lib/pulse/user_informer.rb +27 -0
- data/lib/pulse/utils/blank.rb +53 -0
- data/lib/pulse/version.rb +3 -0
- data/lib/pulse_tasks.rb +64 -0
- data/lib/rails/generators/pulse/pulse_generator.rb +100 -0
- data/lib/templates/javascript_notifier.erb +15 -0
- data/lib/templates/rescue.erb +91 -0
- data/pulse.gemspec +39 -0
- data/rails/init.rb +1 -0
- data/resources/README.md +34 -0
- data/resources/ca-bundle.crt +3376 -0
- data/script/integration_test.rb +38 -0
- data/test/backtrace_test.rb +162 -0
- data/test/capistrano_test.rb +34 -0
- data/test/catcher_test.rb +333 -0
- data/test/configuration_test.rb +236 -0
- data/test/helper.rb +263 -0
- data/test/javascript_notifier_test.rb +51 -0
- data/test/logger_test.rb +79 -0
- data/test/notice_test.rb +490 -0
- data/test/notifier_test.rb +276 -0
- data/test/projectlocker_pulse_tasks_test.rb +170 -0
- data/test/pulse.xsd +88 -0
- data/test/rack_test.rb +58 -0
- data/test/rails_initializer_test.rb +36 -0
- data/test/recursion_test.rb +10 -0
- data/test/sender_test.rb +288 -0
- data/test/user_informer_test.rb +29 -0
- metadata +432 -0
data/lib/pulse/notice.rb
ADDED
@@ -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
|