lib/airbrake-ruby/notice_notifier.rb
module Airbrake
# NoticeNotifier is reponsible for sending notices to Airbrake. It supports
# synchronous and asynchronous delivery.
#
# @see Airbrake::Config The list of options
# @since v1.0.0
# @api public
class NoticeNotifier
# @return [Array<Class>] filters to be executed first
DEFAULT_FILTERS = [
Airbrake::Filters::SystemExitFilter,
Airbrake::Filters::GemRootFilter,
# Optional filters (must be included by users):
# Airbrake::Filters::ThreadFilter
].freeze
include Inspectable
include Loggable
def initialize
@config = Airbrake::Config.instance
@filter_chain = FilterChain.new
@async_sender = AsyncSender.new(:post, self.class.name)
@sync_sender = SyncSender.new
DEFAULT_FILTERS.each { |filter| add_filter(filter.new) }
add_filter(Airbrake::Filters::ContextFilter.new)
add_filter(Airbrake::Filters::ExceptionAttributesFilter.new)
end
# @see Airbrake.notify
def notify(exception, params = {}, &block)
send_notice(exception, params, default_sender, &block)
end
# @see Airbrake.notify_sync
def notify_sync(exception, params = {}, &block)
send_notice(exception, params, @sync_sender, &block).value
end
# @see Airbrake.add_filte
def add_filter(filter = nil, &block)
@filter_chain.add_filter(block_given? ? block : filter)
end
# @see Airbrake.delete_filter
def delete_filter(filter_class)
@filter_chain.delete_filter(filter_class)
end
# @see Airbrake.build_notice
def build_notice(exception, params = {})
if @async_sender.closed?
raise Airbrake::Error,
"Airbrake is closed; can't build exception: " \
"#{exception.class}: #{exception}"
end
if exception.is_a?(Airbrake::Notice)
exception[:params].merge!(params)
exception
else
Notice.new(convert_to_exception(exception), params.dup)
end
end
# @see Airbrake.close
def close
@sync_sender.close
@async_sender.close
end
# @see Airbrake.configured?
def configured?
@config.valid?
end
# @see Airbrake.merge_context
def merge_context(context)
Airbrake::Context.current.merge!(context)
end
# @return [Boolean]
# @since v4.14.0
def has_filter?(filter_class) # rubocop:disable Naming/PredicateName
@filter_chain.includes?(filter_class)
end
private
def convert_to_exception(ex)
if ex.is_a?(Exception) || Backtrace.java_exception?(ex)
# Manually created exceptions don't have backtraces, so we create a fake
# one, whose first frame points to the place where Airbrake was called
# (normally via `notify`).
ex.set_backtrace(clean_backtrace) unless ex.backtrace
return ex
end
e = RuntimeError.new(ex.to_s)
e.set_backtrace(clean_backtrace)
e
end
def send_notice(exception, params, sender)
promise = @config.check_configuration
return promise if promise.rejected?
notice = build_notice(exception, params)
yield notice if block_given?
@filter_chain.refine(notice)
promise = Airbrake::Promise.new
return promise.reject("#{notice} was marked as ignored") if notice.ignored?
sender.send(notice, promise)
end
def default_sender
return @async_sender if @async_sender.has_workers?
logger.warn(
"#{LOG_LABEL} falling back to sync delivery because there are no " \
"running async workers",
)
@sync_sender
end
def clean_backtrace
caller_copy = Kernel.caller
clean_bt = caller_copy.drop_while { |frame| frame.include?('/lib/airbrake') }
# If true, then it's likely an internal library error. In this case return
# at least some backtrace to simplify debugging.
return caller_copy if clean_bt.empty?
clean_bt
end
end
end