pboling/exception_notification

View on GitHub
lib/exception_notification/notifiable.rb

Summary

Maintainability
A
1 hr
Test Coverage
module ExceptionNotification::Notifiable
  include ExceptionNotification::NotifiableHelper

  def self.included(base)
    base.extend ClassMethods

    # Verbosity of the gem
    base.cattr_accessor :notifiable_verbose
    base.notifiable_verbose = false
    # Do Not Ever send error notification emails for these Error Classes
    base.cattr_accessor :notifiable_silent_exceptions
    base.notifiable_silent_exceptions = SILENT_EXCEPTIONS
    # Notification Level
    base.cattr_accessor :notifiable_notification_level
    base.notifiable_notification_level = [:email, :web_hooks]

    # Since there is no concept of locality from a request here allow user to explicitly define which env's are noisy (send notifications)
    base.cattr_accessor :notifiable_noisy_environments
    base.notifiable_noisy_environments = ["production"]

    base.cattr_accessor :notifiable_pass_through
    base.notifiable_pass_through = false
  end

  module ClassMethods
    # set the exception_data deliverer OR retrieve the exception_data
    def exception_data(deliverer = nil)
      if deliverer
        write_inheritable_attribute(:exception_data, deliverer)
      else
        read_inheritable_attribute(:exception_data)
      end
    end

    def be_silent_for_exception?(exception)
      self.notifiable_silent_exceptions.respond_to?(:any?) && self.notifiable_silent_exceptions.any? {|klass| klass === exception }
    end

  end

  # Usage:
  #   notifiable { Klass.some_method }
  # This will rescue any errors that occur within Klass.some_method
  def notifiable(&block)
    yield
  rescue => exception
    rescue_with_hooks(exception)
    raise
  end

  def be_silent_for_exception?(exception)
    self.class.be_silent_for_exception?(exception)
  end

  private

  def environment_is_noisy?
    self.class.notifiable_noisy_environments.include?(Rails.env) if defined?(Rails)
  end

  def notification_level_sends_email?
    self.class.notifiable_notification_level.include?(:email)
  end

  def notification_level_sends_web_hooks?
    self.class.notifiable_notification_level.include?(:web_hooks)
  end

  def rescue_with_hooks(exception)
    verbose = self.class.notifiable_verbose && respond_to?(:logger) && !logger.nil?
    logger.info("[RESCUE STYLE] rescue_with_hooks") if verbose
    data = get_exception_data
    # With ExceptionNotifiable you have an inherent request, and using a status code makes sense.
    # With Notifiable class to wrap around everything that doesn't have a request,
    request = nil
    #   the errors you want to be notified of need to be specified either positively or negatively
    # 1. positive eg. set ExceptionNotification::Notifier.config[:notify_error_classes] to an array of classes
    #               set ExceptionNotification::Notifier.config[:notify_other_errors] to false
    # 1. negative eg. set Klass.silent_exceptions to the ones to keep quiet
    #               set ExceptionNotification::Notifier.config[:notify_other_errors] to true
    status_code = nil
    #We only send email if it has been configured in environment
    send_email = should_email_on_exception?(exception, status_code, verbose)
    #We only send web hooks if they've been configured in environment
    send_web_hooks = should_web_hook_on_exception?(exception, status_code, verbose)
    the_blamed = ExceptionNotification::Notifier.config[:git_repo_path].nil? ? nil : lay_blame(exception)
    rejected_sections = %w(request session)
    verbose_output(exception, status_code, "rescued by handler", send_email, send_web_hooks, nil, the_blamed, rejected_sections) if verbose
    # Send the exception notification email
    perform_exception_notify_mailing(exception, data, nil, the_blamed, verbose, rejected_sections) if send_email
    # Send Web Hook requests
    ExceptionNotification::HooksNotifier.deliver_exception_to_web_hooks(ExceptionNotification::Notifier.config, exception, self, request, data, the_blamed) if send_web_hooks
    pass_it_on(exception, request, verbose)
  end

  def pass_it_on(exception, request = nil, verbose = false)
    begin
      request ||= {:params => {}}
      case self.class.notifiable_pass_through
        when :hoptoad then
          HoptoadNotifier.notify(exception, {:request => request})
          logger.info("[PASS-IT-ON] HOPTOAD NOTIFIED") if verbose
        else
          logger.info("[PASS-IT-ON] NO") if verbose
          #Do Nothing
      end
    rescue
      #Do Nothing
      logger.info("[PASS-IT-ON] FAILED") if verbose
    end
  end

  def is_local? #like asking is_silent?
    !self.notifiable_noisy_environments.include?(Rails.env)
  end

end