2018-10-22 03:00:50 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-08-24 23:06:16 -04:00
|
|
|
module Gitlab
|
2019-12-16 07:07:43 -05:00
|
|
|
module ErrorTracking
|
2020-04-14 20:09:27 -04:00
|
|
|
# Exceptions in this group will receive custom Sentry fingerprinting
|
|
|
|
CUSTOM_FINGERPRINTING = %w[
|
|
|
|
Acme::Client::Error::BadNonce
|
|
|
|
Acme::Client::Error::NotFound
|
|
|
|
Acme::Client::Error::RateLimited
|
|
|
|
Acme::Client::Error::Timeout
|
|
|
|
Acme::Client::Error::UnsupportedOperation
|
|
|
|
ActiveRecord::ConnectionTimeoutError
|
|
|
|
Gitlab::RequestContext::RequestDeadlineExceeded
|
|
|
|
GRPC::DeadlineExceeded
|
|
|
|
JIRA::HTTPError
|
|
|
|
Rack::Timeout::RequestTimeoutException
|
|
|
|
].freeze
|
|
|
|
|
2021-03-31 08:08:55 -04:00
|
|
|
PROCESSORS = [
|
|
|
|
::Gitlab::ErrorTracking::Processor::SidekiqProcessor,
|
|
|
|
::Gitlab::ErrorTracking::Processor::GrpcErrorProcessor,
|
2022-03-31 17:08:16 -04:00
|
|
|
::Gitlab::ErrorTracking::Processor::ContextPayloadProcessor,
|
|
|
|
::Gitlab::ErrorTracking::Processor::SanitizeErrorMessageProcessor
|
2021-03-31 08:08:55 -04:00
|
|
|
].freeze
|
|
|
|
|
2019-12-13 07:07:41 -05:00
|
|
|
class << self
|
2022-03-10 13:09:14 -05:00
|
|
|
def configure(&block)
|
|
|
|
configure_raven(&block)
|
|
|
|
configure_sentry(&block)
|
|
|
|
end
|
|
|
|
|
|
|
|
def configure_raven
|
2021-06-04 11:10:25 -04:00
|
|
|
Raven.configure do |config|
|
2019-12-13 07:07:41 -05:00
|
|
|
config.dsn = sentry_dsn
|
|
|
|
config.release = Gitlab.revision
|
2021-06-04 11:10:25 -04:00
|
|
|
config.current_environment = Gitlab.config.sentry.environment
|
|
|
|
|
|
|
|
# Sanitize fields based on those sanitized from Rails.
|
|
|
|
config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s)
|
|
|
|
|
|
|
|
# Sanitize authentication headers
|
|
|
|
config.sanitize_http_headers = %w[Authorization Private-Token]
|
2022-03-10 13:09:14 -05:00
|
|
|
config.before_send = method(:before_send_raven)
|
|
|
|
|
|
|
|
yield config if block_given?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def configure_sentry
|
|
|
|
Sentry.init do |config|
|
|
|
|
config.dsn = new_sentry_dsn
|
|
|
|
config.release = Gitlab.revision
|
|
|
|
config.environment = new_sentry_environment
|
|
|
|
config.before_send = method(:before_send_sentry)
|
|
|
|
config.background_worker_threads = 0
|
|
|
|
config.send_default_pii = true
|
2020-06-12 08:08:56 -04:00
|
|
|
|
|
|
|
yield config if block_given?
|
2019-12-13 07:07:41 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# This should be used when you want to passthrough exception handling:
|
|
|
|
# rescue and raise to be catched in upper layers of the application.
|
|
|
|
#
|
|
|
|
# If the exception implements the method `sentry_extra_data` and that method
|
|
|
|
# returns a Hash, then the return value of that method will be merged into
|
|
|
|
# `extra`. Exceptions can use this mechanism to provide structured data
|
|
|
|
# to sentry in addition to their message and back-trace.
|
|
|
|
def track_and_raise_exception(exception, extra = {})
|
|
|
|
process_exception(exception, sentry: true, extra: extra)
|
2018-11-14 06:10:36 -05:00
|
|
|
|
2019-12-13 07:07:41 -05:00
|
|
|
raise exception
|
2018-04-13 12:32:54 -04:00
|
|
|
end
|
|
|
|
|
2019-12-13 07:07:41 -05:00
|
|
|
# This can be used for investigating exceptions that can be recovered from in
|
|
|
|
# code. The exception will still be raised in development and test
|
|
|
|
# environments.
|
|
|
|
#
|
|
|
|
# That way we can track down these exceptions with as much information as we
|
|
|
|
# need to resolve them.
|
|
|
|
#
|
|
|
|
# If the exception implements the method `sentry_extra_data` and that method
|
|
|
|
# returns a Hash, then the return value of that method will be merged into
|
|
|
|
# `extra`. Exceptions can use this mechanism to provide structured data
|
|
|
|
# to sentry in addition to their message and back-trace.
|
|
|
|
#
|
|
|
|
# Provide an issue URL for follow up.
|
|
|
|
# as `issue_url: 'http://gitlab.com/gitlab-org/gitlab/issues/111'`
|
|
|
|
def track_and_raise_for_dev_exception(exception, extra = {})
|
|
|
|
process_exception(exception, sentry: true, extra: extra)
|
2019-05-10 17:12:33 -04:00
|
|
|
|
2019-12-13 07:07:41 -05:00
|
|
|
raise exception if should_raise_for_dev?
|
|
|
|
end
|
2019-08-19 19:06:21 -04:00
|
|
|
|
2019-12-13 07:07:41 -05:00
|
|
|
# This should be used when you only want to track the exception.
|
|
|
|
#
|
|
|
|
# If the exception implements the method `sentry_extra_data` and that method
|
|
|
|
# returns a Hash, then the return value of that method will be merged into
|
|
|
|
# `extra`. Exceptions can use this mechanism to provide structured data
|
|
|
|
# to sentry in addition to their message and back-trace.
|
|
|
|
def track_exception(exception, extra = {})
|
|
|
|
process_exception(exception, sentry: true, extra: extra)
|
2019-08-19 19:06:21 -04:00
|
|
|
end
|
|
|
|
|
2019-12-13 07:07:41 -05:00
|
|
|
# This should be used when you only want to log the exception,
|
|
|
|
# but not send it to Sentry.
|
|
|
|
#
|
|
|
|
# If the exception implements the method `sentry_extra_data` and that method
|
|
|
|
# returns a Hash, then the return value of that method will be merged into
|
|
|
|
# `extra`. Exceptions can use this mechanism to provide structured data
|
|
|
|
# to sentry in addition to their message and back-trace.
|
|
|
|
def log_exception(exception, extra = {})
|
|
|
|
process_exception(exception, extra: extra)
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2022-03-10 13:09:14 -05:00
|
|
|
def before_send_raven(event, hint)
|
2022-05-06 11:09:03 -04:00
|
|
|
return unless Feature.enabled?(:enable_old_sentry_integration)
|
2022-03-10 13:09:14 -05:00
|
|
|
|
|
|
|
before_send(event, hint)
|
|
|
|
end
|
|
|
|
|
|
|
|
def before_send_sentry(event, hint)
|
2022-05-06 11:09:03 -04:00
|
|
|
return unless Feature.enabled?(:enable_new_sentry_integration)
|
2022-03-10 13:09:14 -05:00
|
|
|
|
|
|
|
before_send(event, hint)
|
|
|
|
end
|
|
|
|
|
2020-04-14 20:09:27 -04:00
|
|
|
def before_send(event, hint)
|
2021-01-06 04:10:31 -05:00
|
|
|
inject_context_for_exception(event, hint[:exception])
|
|
|
|
custom_fingerprinting(event, hint[:exception])
|
2020-04-14 20:09:27 -04:00
|
|
|
|
2021-03-31 08:08:55 -04:00
|
|
|
PROCESSORS.reduce(event) do |processed_event, processor|
|
|
|
|
processor.call(processed_event)
|
|
|
|
end
|
2020-04-14 20:09:27 -04:00
|
|
|
end
|
|
|
|
|
2019-12-13 07:07:41 -05:00
|
|
|
def process_exception(exception, sentry: false, logging: true, extra:)
|
2021-03-03 07:11:16 -05:00
|
|
|
context_payload = Gitlab::ErrorTracking::ContextPayloadGenerator.generate(exception, extra)
|
2020-02-07 10:09:52 -05:00
|
|
|
|
2021-06-04 11:10:25 -04:00
|
|
|
if sentry && Raven.configuration.server
|
|
|
|
Raven.capture_exception(exception, **context_payload)
|
2019-12-13 07:07:41 -05:00
|
|
|
end
|
2019-08-19 19:06:21 -04:00
|
|
|
|
2022-03-10 13:09:14 -05:00
|
|
|
# There is a possibility that this method is called before Sentry is
|
|
|
|
# configured. Since Sentry 4.0, some methods of Sentry are forwarded to
|
|
|
|
# to `nil`, hence we have to check the client as well.
|
|
|
|
if sentry && ::Sentry.get_current_client && ::Sentry.configuration.dsn
|
|
|
|
::Sentry.capture_exception(exception, **context_payload)
|
|
|
|
end
|
|
|
|
|
2019-12-13 07:07:41 -05:00
|
|
|
if logging
|
2021-03-03 07:11:16 -05:00
|
|
|
formatter = Gitlab::ErrorTracking::LogFormatter.new
|
|
|
|
log_hash = formatter.generate_log(exception, context_payload)
|
2019-12-13 07:07:41 -05:00
|
|
|
|
2019-12-16 07:07:43 -05:00
|
|
|
Gitlab::ErrorTracking::Logger.error(log_hash)
|
2019-12-13 07:07:41 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def sentry_dsn
|
2022-03-10 13:09:14 -05:00
|
|
|
return unless sentry_configurable?
|
2019-12-13 07:07:41 -05:00
|
|
|
return unless Gitlab.config.sentry.enabled
|
|
|
|
|
|
|
|
Gitlab.config.sentry.dsn
|
|
|
|
end
|
|
|
|
|
2022-03-10 13:09:14 -05:00
|
|
|
def new_sentry_dsn
|
|
|
|
return unless sentry_configurable?
|
|
|
|
return unless Gitlab::CurrentSettings.respond_to?(:sentry_enabled?)
|
|
|
|
return unless Gitlab::CurrentSettings.sentry_enabled?
|
|
|
|
|
|
|
|
Gitlab::CurrentSettings.sentry_dsn
|
|
|
|
end
|
|
|
|
|
|
|
|
def new_sentry_environment
|
|
|
|
return unless Gitlab::CurrentSettings.respond_to?(:sentry_environment)
|
|
|
|
|
|
|
|
Gitlab::CurrentSettings.sentry_environment
|
|
|
|
end
|
|
|
|
|
|
|
|
def sentry_configurable?
|
|
|
|
Rails.env.production? || Rails.env.development?
|
|
|
|
end
|
|
|
|
|
2019-12-13 07:07:41 -05:00
|
|
|
def should_raise_for_dev?
|
|
|
|
Rails.env.development? || Rails.env.test?
|
|
|
|
end
|
|
|
|
|
2021-01-06 04:10:31 -05:00
|
|
|
# Group common, mostly non-actionable exceptions by type and message,
|
|
|
|
# rather than cause
|
|
|
|
def custom_fingerprinting(event, ex)
|
|
|
|
return event unless CUSTOM_FINGERPRINTING.include?(ex.class.name)
|
|
|
|
|
|
|
|
event.fingerprint = [ex.class.name, ex.message]
|
|
|
|
end
|
|
|
|
|
|
|
|
def inject_context_for_exception(event, ex)
|
2021-12-16 13:14:09 -05:00
|
|
|
sql = Gitlab::ExceptionLogFormatter.find_sql(ex)
|
2021-12-10 16:11:20 -05:00
|
|
|
|
2021-12-16 13:14:09 -05:00
|
|
|
event.extra[:sql] = sql if sql
|
2021-12-10 16:11:20 -05:00
|
|
|
end
|
2019-12-13 07:07:41 -05:00
|
|
|
end
|
2016-08-24 23:06:16 -04:00
|
|
|
end
|
|
|
|
end
|