Skip to content

Instantly share code, notes, and snippets.

@RStankov
Created May 22, 2024 10:52
Show Gist options
  • Save RStankov/2b76e1d3e6631db2732887e9cecd3151 to your computer and use it in GitHub Desktop.
Save RStankov/2b76e1d3e6631db2732887e9cecd3151 to your computer and use it in GitHub Desktop.
Exception tips
# frozen_string_literal: true
module ErrorReporting
extend self
def assign_user(user)
return unless Rails.env.production?
Sentry.set_user(
id: user.id,
username: user.username,
admin: user.admin?,
)
end
def capture(error, context = {})
raise error unless Rails.env.production?
context.each { |k, v| Sentry.set_context(k, v) }
if error.is_a?(String)
Sentry.capture_message(error)
else
Sentry.capture_exception(error)
end
end
def job_discarded(job, exception)
Rails.logger.error "Discarded #{job.class} due to #{exception.cause.inspect}."
capture(exception, job_name: job.class.name, job_arguments: job.arguments)
end
end
# NOTE(rstankov): Browsers send params in wrong encoding, which causes `invalid byte sequence in UTF-8`
# More info: https://robots.thoughtbot.com/fight-back-utf-8-invalid-byte-sequences
module Handle::InvalidByteSequence
extend self
def call(string)
return nil if string.nil?
string.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')
end
end
module AngrySupport::Handle::Job::DeserializationError
def self.included(klass)
# NOTE(rstankov): Some time job is executed, before record is commit to database or record was deleted before backround jobs has been processed
klass.retry_on ActiveJob::DeserializationError, wait: 2.minutes, attempts: 3 do |job, exception|
ErrorReporting.job_discarded(job, exception)
end
end
end
module AngrySupport::Handle::Job::NetworkErrors
extend ActiveSupport::Concern
included do
retry_on(*::AngrySupport::Handle::NetworkErrors::ERRORS, wait: 2.minutes, attempts: 5) do |job, exception|
ErrorReporting.job_discarded(job, exception)
end
end
end
module Handle::NetworkErrors
extend self
ERRORS = [
Addressable::URI::InvalidURIError,
EOFError,
Errno::EADDRNOTAVAIL,
Errno::ECONNREFUSED,
Errno::ECONNRESET,
Errno::EFAULT,
Errno::EHOSTUNREACH,
Errno::EINVAL,
Errno::EMFILE,
Errno::ENETUNREACH,
Errno::EPIPE,
Errno::ETIMEDOUT,
Faraday::ConnectionFailed,
Faraday::TimeoutError,
HTTP::ConnectionError,
HTTParty::Error,
IOError,
JSON::ParserError,
Net::HTTPBadResponse,
Net::HTTPHeaderSyntaxError,
Net::OpenTimeout,
Net::ProtocolError,
Net::ReadTimeout,
OpenSSL::SSL::SSLError,
OpenURI::HTTPError,
RestClient::BadGateway,
RestClient::BadRequest,
RestClient::Conflict,
RestClient::Exceptions::OpenTimeout,
RestClient::Exceptions::ReadTimeout,
RestClient::Forbidden,
RestClient::GatewayTimeout,
RestClient::InternalServerError,
RestClient::NotFound,
RestClient::RequestFailed,
RestClient::ServerBrokeConnection,
RestClient::ServiceUnavailable,
SocketError,
Timeout::Error,
Zlib::BufError,
Zlib::DataError,
].freeze
def ===(error)
ERRORS.any? { |error_class| error.is_a?(error_class) }
end
def call(fallback:)
yield
rescue *ERRORS => _e
fallback
end
end
module Handle::RaceCondition
extend self
def call(max_retries: 2, transaction: false)
retries ||= max_retries
if transaction
ActiveRecord::Base.transaction do
yield
end
else
yield
end
rescue ActiveRecord::RecordNotUnique, PG::UniqueViolation, PG::InFailedSqlTransaction
retries -= 1
raise unless retries.nonzero?
retry
rescue ActiveRecord::RecordInvalid => e
raise unless e.message.include? I18n.t('errors.messages.taken')
retries -= 1
raise unless retries.nonzero?
retry
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment