1 2 3 4 5 6 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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
|
# frozen_string_literal: true
require "logger"
require "active_support/core_ext/string/inflections"
require "active_support/core_ext/module/attribute_accessors"
require "exception_notifier/base_notifier"
require "exception_notifier/modules/error_grouping"
module ExceptionNotifier
include ErrorGrouping
autoload :BacktraceCleaner, "exception_notifier/modules/backtrace_cleaner"
autoload :Formatter, "exception_notifier/modules/formatter"
autoload :Notifier, "exception_notifier/notifier"
autoload :EmailNotifier, "exception_notifier/email_notifier"
autoload :HipchatNotifier, "exception_notifier/hipchat_notifier"
autoload :WebhookNotifier, "exception_notifier/webhook_notifier"
autoload :IrcNotifier, "exception_notifier/irc_notifier"
autoload :SlackNotifier, "exception_notifier/slack_notifier"
autoload :MattermostNotifier, "exception_notifier/mattermost_notifier"
autoload :TeamsNotifier, "exception_notifier/teams_notifier"
autoload :SnsNotifier, "exception_notifier/sns_notifier"
autoload :GoogleChatNotifier, "exception_notifier/google_chat_notifier"
autoload :DatadogNotifier, "exception_notifier/datadog_notifier"
class UndefinedNotifierError < StandardError; end
# Define logger
mattr_accessor :logger
@@logger = Logger.new($stdout)
# Define a set of exceptions to be ignored, ie, dont send notifications when any of them are raised.
mattr_accessor :ignored_exceptions
@@ignored_exceptions = %w[
ActiveRecord::RecordNotFound Mongoid::Errors::DocumentNotFound AbstractController::ActionNotFound
ActionController::RoutingError ActionController::UnknownFormat ActionController::UrlGenerationError
ActionDispatch::Http::MimeNegotiation::InvalidType Rack::Utils::InvalidParameterError
]
mattr_accessor :testing_mode
@@testing_mode = false
class << self
# Store conditions that decide when exceptions must be ignored or not.
@@ignores = []
# Store by-notifier conditions that decide when exceptions must be ignored or not.
@@by_notifier_ignores = {}
# Store notifiers that send notifications when exceptions are raised.
@@notifiers = {}
def testing_mode!
self.testing_mode = true
end
def notify_exception(exception, options = {}, &block)
return false if ignored_exception?(options[:ignore_exceptions], exception)
return false if ignored?(exception, options)
if error_grouping
errors_count = group_error!(exception, options)
return false unless send_notification?(exception, errors_count)
end
notification_fired = false
selected_notifiers = options.delete(:notifiers) || notifiers
[*selected_notifiers].each do |notifier|
unless notifier_ignored?(exception, options, notifier: notifier)
fire_notification(notifier, exception, options.dup, &block)
notification_fired = true
end
end
notification_fired
end
def register_exception_notifier(name, notifier_or_options)
if notifier_or_options.respond_to?(:call)
@@notifiers[name] = notifier_or_options
elsif notifier_or_options.is_a?(Hash)
create_and_register_notifier(name, notifier_or_options)
else
raise ArgumentError, "Invalid notifier '#{name}' defined as #{notifier_or_options.inspect}"
end
end
alias_method :add_notifier, :register_exception_notifier
def unregister_exception_notifier(name)
@@notifiers.delete(name)
end
def registered_exception_notifier(name)
@@notifiers[name]
end
def notifiers
@@notifiers.keys
end
# Adds a condition to decide when an exception must be ignored or not.
#
# ExceptionNotifier.ignore_if do |exception, options|
# not Rails.env.production?
# end
def ignore_if(&block)
@@ignores << block
end
def ignore_notifier_if(notifier, &block)
@@by_notifier_ignores[notifier] = block
end
def ignore_crawlers(crawlers)
ignore_if do |_exception, opts|
opts.key?(:env) && from_crawler(opts[:env], crawlers)
end
end
def clear_ignore_conditions!
@@ignores.clear
@@by_notifier_ignores.clear
end
private
def ignored?(exception, options)
@@ignores.any? { |condition| condition.call(exception, options) }
rescue Exception => e # standard:disable Lint/RescueException
raise e if @@testing_mode
logger.warn(
"An error occurred when evaluating an ignore condition. #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
)
false
end
def notifier_ignored?(exception, options, notifier:)
return false unless @@by_notifier_ignores.key?(notifier)
condition = @@by_notifier_ignores[notifier]
condition.call(exception, options)
rescue Exception => e # standard:disable Lint/RescueException
raise e if @@testing_mode
logger.warn(<<~"MESSAGE")
An error occurred when evaluating a by-notifier ignore condition. #{e.class}: #{e.message}
#{e.backtrace.join("\n")}
MESSAGE
false
end
def ignored_exception?(ignore_array, exception)
all_ignored_exceptions = (Array(ignored_exceptions) + Array(ignore_array)).map(&:to_s)
exception_ancestors = exception.singleton_class.ancestors.map(&:to_s)
!(all_ignored_exceptions & exception_ancestors).empty?
end
def fire_notification(notifier_name, exception, options, &block)
notifier = registered_exception_notifier(notifier_name)
notifier.call(exception, options, &block)
rescue Exception => e # standard:disable Lint/RescueException
raise e if @@testing_mode
logger.warn(
"An error occurred when sending a notification using '#{notifier_name}' notifier." \
"#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
)
false
end
def create_and_register_notifier(name, options)
notifier_classname = "#{name}_notifier".camelize
notifier_class = ExceptionNotifier.const_get(notifier_classname)
notifier = notifier_class.new(options)
register_exception_notifier(name, notifier)
rescue NameError => e
raise UndefinedNotifierError,
"No notifier named '#{name}' was found. Please, revise your configuration options. Cause: #{e.message}"
end
def from_crawler(env, ignored_crawlers)
agent = env["HTTP_USER_AGENT"]
Array(ignored_crawlers).any? do |crawler|
agent =~ Regexp.new(crawler)
end
end
end
end
|