Skip to content

Instantly share code, notes, and snippets.

@leandro
Created September 27, 2022 17:23
Show Gist options
  • Save leandro/644cc3ca3c25f727fb411c9aae3d54cb to your computer and use it in GitHub Desktop.
Save leandro/644cc3ca3c25f727fb411c9aae3d54cb to your computer and use it in GitHub Desktop.
Sentry toggling extensiion
require 'sentry_extensions/toggler'
require 'sentry_extensions/configuration'
Sentry.init do |config|
config.dsn = ENV['SENTRY_API_KEY']
config.excluded_exceptions = config.excluded_exceptions - ['ActiveRecord::RecordNotFound']
# This allows us to skip an error to be sent to Sentry, which is useful for
# when we're already manually sending the error to Sentry and then re-raising
# the error.
config.before_send = lambda do |event, hint|
e = hint[:exception]
can_report = e.respond_to?(:report_to_sentry?) ? e.report_to_sentry? : true
event if can_report
end
end
module SentryExtensions
module Configuration
def before_send=(value)
super
original_before_send = @before_send
@before_send = ->(event, hint) do
send_to_sentry = Sentry.can_report_event?(event)
Sentry.set_old_toggler_data_back
original_before_send.call(event, hint) if send_to_sentry
end
end
end
end
Sentry::Configuration.prepend(SentryExtensions::Configuration)
module SentryExtensions
module Toggler
VAR_NAME = :sentry_toggler
private_constant :VAR_NAME
def self.extended(base)
base.class_exec do
class << self
alias original_capture_exception capture_exception
alias original_capture_message capture_message
remove_method(:capture_exception)
remove_method(:capture_message)
end
end
end
def disable_reporting(...) = toggle_reporting(false, ...)
def enable_reporting(...) = toggle_reporting(...)
def put_back_old_toggler_data? = !!get_thread_data&.dig(:reversible)
# If +can_report+ is +false+ then event won't be reported if any of the
# filters matches against the event, otherwise the event will be sent.
# Conversely, if +can_report+ is +true+ and any of the filters match the
# event, it'll be sent, otherwise it won't be sent.
def can_report_event?(event)
data = toggler_data
can_report, filters = data.values_at(:can_report, :filters)
return can_report if filters.blank?
any_filter_satisfied =
filters.symbolize_keys.slice(:message, :extra).any? do |key, block|
unless block.is_a?(Proc)
raise ArgumentError, "Filter :#{key} is not a Proc"
end
block.call(event.public_send(key))
end
any_filter_satisfied ? can_report : !can_report
end
def capture_exception(...)
set_old_toggler_data_back_after_event_is_sent
original_capture_exception(...)
end
def capture_message(...)
set_old_toggler_data_back_after_event_is_sent
original_capture_message(...)
end
def can_revert_toggler_data?
(revert = revert_after_event_is_sent).nil? || revert
end
def revert_after_event_is_sent
get_thread_data&.dig(:revert_after_event_is_sent)
end
def set_old_toggler_data_back(from_after_yield = false)
return if from_after_yield && revert_after_event_is_sent == true
return if !put_back_old_toggler_data? || !can_revert_toggler_data?
set_toggler_data(get_thread_data&.dig(:old), true)
end
def toggler_data
(get_thread_data || set_toggler_data(can_report: true))&.dig(:current)
end
private
def get_thread_data
main, current = [Thread.main, Thread.current]
if main != current && main[VAR_NAME] != current[VAR_NAME]
current[VAR_NAME] = main[VAR_NAME]&.deep_dup&.freeze
end
current[VAR_NAME]
end
def set_old_toggler_data_back_after_event_is_sent
return unless put_back_old_toggler_data?
set_thread_data(get_thread_data&.merge(revert_after_event_is_sent: true))
end
def set_reversible_data(revert)
set_thread_data(get_thread_data&.merge(reversible: !!revert))
end
def set_thread_data(value)
threads = [Thread.main, Thread.current].uniq
threads.map { _1[VAR_NAME] = value.deep_dup.freeze }.last
end
def set_toggler_data(value, clear_old = false)
return if value.nil?
current = value.freeze
payload = get_thread_data || {}
old = !clear_old && payload[:current] || { can_report: true }.freeze
set_thread_data({ current: current, old: old }.compact)
end
def toggle_reporting(enable = true, filters = {})
payload = { can_report: !!enable, filters: filters.dup.presence&.freeze }
set_toggler_data(payload.compact, !block_given?)
set_reversible_data(block_given?)
return unless block_given?
begin
yield.tap { set_old_toggler_data_back(true) }
rescue Exception
set_old_toggler_data_back_after_event_is_sent
raise
end
end
end
end
Sentry.extend(SentryExtensions::Toggler)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment