Module: Wrangler
- Defined in:
- lib/wrangler/wrangler_exceptions.rb,
lib/wrangler.rb,
lib/wrangler/wrangler_helper.rb,
lib/wrangler/exception_handler.rb,
lib/wrangler/exception_notifier.rb
Overview
a bunch of handy exceptions. using the Http ones will require rails
Defined Under Namespace
Modules: ControllerMethods, LocalControllerMethods, PublicControllerMethods Classes: ExceptionHandler, ExceptionNotifier, HttpForbidden, HttpInternalServerError, HttpNotAcceptable, HttpNotFound, HttpNotImplemented, HttpServiceUnavailable, HttpStatusError, HttpUnauthorized
Constant Summary collapse
- WRANGLER_ROOT =
"#{File.dirname(__FILE__)}/../.."
Class Method Summary collapse
-
.class_has_ancestor?(klass, other_klasses) ⇒ Boolean
utility to determine if a class has another class as its ancestor.
-
.codes_for_exception_classes ⇒ Object
A utility method that should only be used internal to wrangler.
-
.config ⇒ Object
shorthand access to the exception handling config —————————————————————————–.
-
.find_file_matching_pattern(search_dirs, pattern) ⇒ Object
given an array of search directory strings (or a single directory string), searches for files matching pattern.
-
.handle_error(error_messages, options = {}) ⇒ Object
publicly available method for explicitly telling wrangler to handle a specific error condition without an actual exception.
-
.handle_exception(exception, options = {}) ⇒ Object
the main exception-handling method.
- .included(base) ⇒ Object
-
.log_error(msgs) ⇒ Object
handles logging error messages, using logger if available and puts otherwise —————————————————————————–.
-
.log_exception(exception, request_data = nil, status_code = nil, error_messages = nil) ⇒ Object
log the exception using logger if available.
-
.notify_in_context? ⇒ Boolean
determine if the current context (
local?
,background
) indicates that a notification should be sent. -
.notify_on_error(proc_name = nil, message = nil, &block) ⇒ Object
execute the code block passed as an argument, and follow notification rules if an exception bubbles out of the block.
-
.notify_on_exception?(exception, status_code = nil) ⇒ Boolean
determine if the app is configured to notify for the given exception or status code —————————————————————————–.
-
.notify_with_delayed_job? ⇒ Boolean
determine if email should be sent with delayed job or not (delayed job must be installed and config set to use delayed job —————————————————————————–.
-
.send_notification?(exception = nil, request = nil, status_code = nil) ⇒ Boolean
determine if a notificaiton should be sent —————————————————————————–.
Instance Method Summary collapse
-
#request_data_from_request(request) ⇒ Object
extract a hash of relevant (and serializable) parameters from a request NOTE: will obey
filter_paramters
on any class including the module, avoid logging any data in the request that the app wouldn’t log itself. -
#skip_request_env?(request_param) ⇒ Boolean
determine if the request env param should be ommitted from the request data object, as specified in config (either for aesthetic reasons or because the param won’t serialize well).
Class Method Details
.class_has_ancestor?(klass, other_klasses) ⇒ Boolean
utility to determine if a class has another class as its ancestor.
returns the ancestor class (if any) that is found to be the ancestor of klass (will return the nearest ancestor in other_klasses). returns nil or false otherwise.
arguments:
- klass: the class we're determining whether it has one of the other
classes as an ancestor
- other_klasses: a Class, an Array (or any other container that responds
to include?() ) of Classes
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/wrangler/wrangler_helper.rb', line 28 def class_has_ancestor?(klass, other_klasses) return nil if !klass.is_a?(Class) other_klasses = [other_klasses] unless other_klasses.is_a?(Array) if other_klasses.first.is_a?(Class) other_klasses.map! { |k| k.name } end current_klass = klass while current_klass return current_klass if other_klasses.include?(current_klass.name) current_klass = current_klass.superclass end return false end |
.codes_for_exception_classes ⇒ Object
A utility method that should only be used internal to wrangler. don’t call this; it should only be called once by the Config class and you can get/set it there. returns a mapping from exception classes to http status codes
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/wrangler/exception_handler.rb', line 7 def self.codes_for_exception_classes classes = { # These are standard errors in rails / ruby NameError.name => "503", TypeError.name => "503", RuntimeError.name => "500", ArgumentError.name => "500", # the default mapping for an unrecognized exception class :default => "500" } # from exception_notification gem: # Highly dependent on the verison of rails, so we're very protective about these' classes.merge!({ ActionView::TemplateError.name => "500"}) if defined?(ActionView) && ActionView.const_defined?(:TemplateError) classes.merge!({ ActiveRecord::RecordNotFound.name => "400" }) if defined?(ActiveRecord) && ActiveRecord.const_defined?(:RecordNotFound) classes.merge!({ ActiveResource::ResourceNotFound.name => "404" }) if defined?(ActiveResource) && ActiveResource.const_defined?(:ResourceNotFound) # from exception_notification gem: if defined?(ActionController) classes.merge!({ ActionController::UnknownController.name => "404" }) if ActionController.const_defined?(:UnknownController) classes.merge!({ ActionController::MissingTemplate.name => "404" }) if ActionController.const_defined?(:MissingTemplate) classes.merge!({ ActionController::MethodNotAllowed.name => "405" }) if ActionController.const_defined?(:MethodNotAllowed) classes.merge!({ ActionController::UnknownAction.name => "501" }) if ActionController.const_defined?(:UnknownAction) classes.merge!({ ActionController::RoutingError.name => "404" }) if ActionController.const_defined?(:RoutingError) classes.merge!({ ActionController::InvalidAuthenticityToken.name => "405" }) if ActionController.const_defined?(:InvalidAuthenticityToken) end return classes end |
.config ⇒ Object
shorthand access to the exception handling config
117 118 119 |
# File 'lib/wrangler/wrangler_helper.rb', line 117 def config Wrangler::ExceptionHandler.config end |
.find_file_matching_pattern(search_dirs, pattern) ⇒ Object
given an array of search directory strings (or a single directory string), searches for files matching pattern.
pattern expressed in cmd line wildcards…like “*.rb” or “foo.?”… and may contain subdirectories.
52 53 54 55 56 57 58 59 60 |
# File 'lib/wrangler/wrangler_helper.rb', line 52 def find_file_matching_pattern(search_dirs, pattern) search_dirs = [search_dirs] unless search_dirs.is_a?(Array) search_dirs.each do |d| matches = Dir.glob(File.join(d, pattern)) return matches.first if matches.size > 0 end return nil end |
.handle_error(error_messages, options = {}) ⇒ Object
publicly available method for explicitly telling wrangler to handle a specific error condition without an actual exception. it’s useful if you want to send a notification after detecting an error condition, but don’t want to interrupt the stack by raising an exception. if you did catch an exception and want to do somethign similar, just call handle_exception diretly.
the error condition will get logged and may result in notification, according to configuration see notify_on_exception?
arguments
- error_messages
-
a message or array of messages (each gets logged on separate log call) capturing the error condition that occurred. this will get logged AND sent in any notifications sent
options
also, any of the options accepted by handle_exception
250 251 252 253 |
# File 'lib/wrangler/exception_handler.rb', line 250 def handle_error(, = {}) .merge! :error_messages => handle_exception(nil, ) end |
.handle_exception(exception, options = {}) ⇒ Object
the main exception-handling method. decides whether to notify or not, whether to render an error page or not, and to make it happen.
arguments
- exception
-
the exception that was caught. can be nil, but should only be nil if notifications should always be sent, as notification rules are bypassed this case
options
- error_messages
-
any additional message to log and send in notification. can also be an array of messages (each gets logged separately)
- request
-
the request object (if any) that resulted in the exception
- render_errors
-
boolean indicating if an error page should be rendered or not (Rails only). default => false
- proc_name
-
a string representation of the process/app that was running when the exception was raised. default value is Wrangler::ExceptionHandler.config.
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 |
# File 'lib/wrangler/exception_handler.rb', line 275 def handle_exception(exception, = {}) request = [:request] render_errors = [:render_errors] || false proc_name = [:proc_name] || config[:app_name] = [:error_messages] || [''] = [] unless .is_a?(Array) if exception.respond_to?(:backtrace) backtrace = exception.backtrace else backtrace = caller end # extract the relevant request data and also filter out any params # that should NOT be logged/emailed (passwords etc.) request_data = request_data_from_request(request) unless request.nil? supplementary_info = nil unless config[:call_for_supplementary_info].nil? supplementary_info = config[:call_for_supplementary_info].call(request) supplementary_info = [supplementary_info] unless supplementary_info.is_a?(Array) end unless supplementary_info.blank? << "Supplementary info:" += supplementary_info end if exception.nil? exception_classname = nil status_code = nil log_error .inspect log_error backtrace log_error "Request params were:" log_error request_data.to_yaml error_string = .shift else status_code = Wrangler::ExceptionHandler.status_code_for_exception(exception) log_exception(exception, request_data, status_code, ) if exception.is_a?(Class) exception_classname = exception.name else exception_classname = exception.class.name end if exception.respond_to?(:message) error_string = exception. else error_string = exception.to_s end end if send_notification?(exception, request, status_code) if notify_with_delayed_job? # don't pass in request as it contains not-easily-serializable stuff log_error "Wrangler sending email notification asynchronously" Wrangler::ExceptionNotifier.send_later(:deliver_exception_notification, exception_classname, error_string, , proc_name, backtrace, supplementary_info, status_code, request_data) else log_error "Wrangler sending email notification synchronously" Wrangler::ExceptionNotifier.deliver_exception_notification(exception_classname, error_string, , proc_name, backtrace, supplementary_info, status_code, request_data, request) end end if render_errors render_error_template(exception, status_code) end rescue Exception => unhandled_exception # if it looks like a temporary error interacting with SMTP, then enqueue # the error using delayed job if possible # (testing by name this way in case the exception isn't loaded into # environment, which would cause a NameError and be counterproductive...) if unhandled_exception.class.name == 'Net::SMTPAuthenticationError' && Wrangler::ExceptionNotifier.respond_to?(:send_later) log_error "Wrangler failed to send error notification: #{unhandled_exception.class.name}:" log_error " #{unhandled_exception.to_s}" # note: this is specific to an old-ish version of delayed job...should # make wrangler compatible with the old and the new... log_error "Wrangler attempting to send via delayed job" Wrangler::ExceptionNotifier.send_later(:deliver_exception_notification, exception_classname, error_string, , proc_name, backtrace, supplementary_info, status_code, request_data) else log_error "/!\\ FAILSAFE /!\\ Wrangler encountered an unhandled exception " + "while trying to handle an error. The arguments it received " + "were:" log_error " exception: #{exception.inspect}" log_error " options: #{.inspect}" log_error "The unhandled error encountered was #{unhandled_exception.class.name}:" log_error " #{unhandled_exception.to_s}" end end |
.included(base) ⇒ Object
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/wrangler.rb', line 12 def self.included(base) # only add in the controller-specific methods if the including class is one if defined?(Rails) && class_has_ancestor?(base, ActionController::Base) base.send(:include, ControllerMethods) # conditionally including these methods (each wrapped in a separate # module) based on the configuration of whether to handle exceptions in # the given environment or not. this allows the default implementation # of the two rescue methods to run when Wrangler-based exception handling # is disabled. if Wrangler::ExceptionHandler.config[:handle_public_errors] Rails.logger.info "Configuring #{base.name} with Wrangler's rescue_action_in_public" base.send(:include, PublicControllerMethods) else Rails.logger.info "NOT Configuring #{base.name} with Wrangler's rescue_action_in_public" end if Wrangler::ExceptionHandler.config[:handle_local_errors] Rails.logger.info "Configuring #{base.name} with Wrangler's rescue_action_locally" base.send(:include, LocalControllerMethods) else Rails.logger.info "NOT configuring #{base.name} with Wrangler's rescue_action_locally" end end end |
.log_error(msgs) ⇒ Object
handles logging error messages, using logger if available and puts otherwise
100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/wrangler/wrangler_helper.rb', line 100 def log_error(msgs) unless msgs.is_a?(Array) msgs = [msgs] end msgs.each do |m| if respond_to?(:logger) && !logger.blank? logger.error m else puts m end end end |
.log_exception(exception, request_data = nil, status_code = nil, error_messages = nil) ⇒ Object
log the exception using logger if available. if object does not have a logger, will just puts()
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/wrangler/wrangler_helper.rb', line 66 def log_exception(exception, request_data = nil, status_code = nil, = nil) msgs = [] msgs << "An exception was caught (#{exception.class.name}):" if exception.respond_to?(:message) msgs << exception. else msgs << exception.to_s end if .is_a?(Array) msgs.concat elsif !.nil? && !.empty? msgs << end unless request_data.blank? msgs << "Request params were:" msgs << request_data.inspect end unless status_code.blank? msgs << "Handling with status code: #{status_code}" end if exception.respond_to?(:backtrace) && !exception.backtrace.blank? msgs << exception.backtrace.join("\n ") end log_error msgs end |
.notify_in_context? ⇒ Boolean
determine if the current context (local?
, background
) indicates that a notification should be sent. this applies all of the rules around notifications EXCEPT for what the current exception or status code is (see notify_on_exception?
for that)
443 444 445 446 447 448 449 450 451 452 453 454 455 456 |
# File 'lib/wrangler/exception_handler.rb', line 443 def notify_in_context? if self.respond_to?(:local_request?) if (local_request? && config[:notify_on_local_error]) || (!local_request? && config[:notify_on_public_error]) notify = true else notify = false end else notify = config[:notify_on_background_error] end return notify end |
.notify_on_error(proc_name = nil, message = nil, &block) ⇒ Object
execute the code block passed as an argument, and follow notification rules if an exception bubbles out of the block.
arguments
- proc_name
-
a name for the chunk of code you’re running, included in logs and in the email notifications’ subject line. optional, default is nil (nothing will be displayed).
- message
-
a message to include in any logs regarding exceptions thrown. useful to explain what the context of the code was to help diagnose. optional, default is nil (no message will be displayed other than the exception’s own message).
return value
-
if an exception bubbles out of the block, the exception is re-raised to calling code.
-
otherwise, returns nil
217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/wrangler/exception_handler.rb', line 217 def notify_on_error(proc_name = nil, = nil, &block) begin yield rescue => exception = {} .merge! :proc_name => proc_name unless proc_name.nil? .merge! :error_messages => unless .nil? handle_exception(exception, ) end return nil end |
.notify_on_exception?(exception, status_code = nil) ⇒ Boolean
determine if the app is configured to notify for the given exception or status code
462 463 464 465 466 467 468 469 470 471 472 |
# File 'lib/wrangler/exception_handler.rb', line 462 def notify_on_exception?(exception, status_code = nil) # first determine if we're configured to notify given the context of the # exception notify = notify_in_context? # now if config says notify in this case, check if we're configured to # notify for this exception or this status code return notify && (config[:notify_exception_classes].include?(exception.class) || config[:notify_status_codes].include?(status_code)) end |
.notify_with_delayed_job? ⇒ Boolean
determine if email should be sent with delayed job or not (delayed job must be installed and config set to use delayed job
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 |
# File 'lib/wrangler/exception_handler.rb', line 477 def notify_with_delayed_job? use_dj = false if self.is_a?(ActionController::Base) if config[:delayed_job_for_controller_errors] && ExceptionNotifier.respond_to?(:send_later) use_dj = true else use_dj = false end else if config[:delayed_job_for_non_controller_errors] && ExceptionNotifier.respond_to?(:send_later) use_dj = true else use_dj = false end end return use_dj end |
.send_notification?(exception = nil, request = nil, status_code = nil) ⇒ Boolean
determine if a notificaiton should be sent
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 |
# File 'lib/wrangler/exception_handler.rb', line 400 def send_notification?(exception = nil, request = nil, status_code = nil) send_notification = notify_in_context? if !send_notification log_error("Will not notify because notify_in_context? returned false") end if send_notification && !exception.nil? send_notification &&= notify_on_exception?(exception, status_code) if !send_notification log_error("Will not notify because notify_on_exception? returned false") end end if send_notification && !request.nil? config[:block_notify_on_request_headers].each do |headers_and_patterns| headers_and_patterns.each_pair do |header, regexp| # only send notification if the header does NOT match the regexp if request.env.include?(header) send_notification &&= (regexp !~ request.env[header]) end if request.env['action_controller.request.query_parameters'].include?(header) send_notification &&= (regexp !~ request.env['action_controller.request.query_parameters'][header]) end if request.env['action_controller.request.request_parameters'].include?(header) send_notification &&= (regexp !~ request.env['action_controller.request.request_parameters'][header]) end end end if !send_notification log_error "Will not notify because :block_notify_on_request_headers was configured to block this request" end end return send_notification end |
Instance Method Details
#request_data_from_request(request) ⇒ Object
extract a hash of relevant (and serializable) parameters from a request NOTE: will obey filter_paramters
on any class including the module, avoid logging any data in the request that the app wouldn’t log itself. filter_paramters
must follow the rails convention of returning the association but with the value obscured in some way (e.g. “[FILTERED]”). see filter_paramter_logging
.
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 |
# File 'lib/wrangler.rb', line 247 def request_data_from_request(request) return nil if request.nil? request_data = {} request.env.each_pair do |k,v| next if skip_request_env?(k) if self.respond_to?(:filter_parameters) request_data.merge! self.send(:filter_parameters, k => v) else request_data.merge! k => v end end request_params = {} if self.respond_to?(:filter_parameters) request_params.merge!( filter_parameters(request.env['action_controller.request.query_parameters']) ) request_params.merge!( filter_parameters(request.env['action_controller.request.request_parameters']) ) else request_params.merge! request.env['action_controller.request.query_parameters'] request_params.merge! request.env['action_controller.request.request_parameters'] end request_data.merge! :params => request_params unless request_params.blank? return request_data end |
#skip_request_env?(request_param) ⇒ Boolean
determine if the request env param should be ommitted from the request data object, as specified in config (either for aesthetic reasons or because the param won’t serialize well).
284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/wrangler.rb', line 284 def skip_request_env?(request_param) skip_env = false Wrangler::ExceptionHandler.config[:request_env_to_skip].each do |pattern| if (pattern.is_a?(String) && pattern == request_param) || (pattern.is_a?(Regexp) && pattern =~ request_param) skip_env = true break end end return skip_env end |