summaryrefslogtreecommitdiff
path: root/lib/gitlab/sentry.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gitlab/sentry.rb')
-rw-r--r--lib/gitlab/sentry.rb193
1 files changed, 135 insertions, 58 deletions
diff --git a/lib/gitlab/sentry.rb b/lib/gitlab/sentry.rb
index 005cb3112b8..aa8f6fa12b1 100644
--- a/lib/gitlab/sentry.rb
+++ b/lib/gitlab/sentry.rb
@@ -2,76 +2,153 @@
module Gitlab
module Sentry
- def self.enabled?
- (Rails.env.production? || Rails.env.development?) &&
- Gitlab.config.sentry.enabled
- end
+ class << self
+ def configure
+ Raven.configure do |config|
+ config.dsn = sentry_dsn
+ config.release = Gitlab.revision
+ 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]
+ config.tags = { program: Gitlab.process_name }
+ # Debugging for https://gitlab.com/gitlab-org/gitlab-foss/issues/57727
+ config.before_send = method(:add_context_from_exception_type)
+ end
+ end
+
+ def with_context(current_user = nil)
+ last_user_context = Raven.context.user
- def self.context(current_user = nil)
- return unless enabled?
+ user_context = {
+ id: current_user&.id,
+ email: current_user&.email,
+ username: current_user&.username
+ }.compact
- Raven.tags_context(default_tags)
+ Raven.tags_context(default_tags)
+ Raven.user_context(user_context)
- if current_user
- Raven.user_context(
- id: current_user.id,
- email: current_user.email,
- username: current_user.username
- )
+ yield
+ ensure
+ Raven.user_context(last_user_context)
end
- end
- # 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.
- #
- # Provide an issue URL for follow up.
- def self.track_exception(exception, issue_url: nil, extra: {})
- track_acceptable_exception(exception, issue_url: issue_url, extra: extra)
-
- raise exception if should_raise_for_dev?
- 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)
- # This should be used when you do not want to raise an exception in
- # development and test. If you need development and test to behave
- # just the same as production you can use this instead of
- # track_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 self.track_acceptable_exception(exception, issue_url: nil, extra: {})
- if enabled?
- extra = build_extra_data(exception, issue_url, extra)
- context # Make sure we've set everything we know in the context
-
- Raven.capture_exception(exception, tags: default_tags, extra: extra)
+ raise exception
end
- end
- def self.should_raise_for_dev?
- Rails.env.development? || Rails.env.test?
- end
+ # 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)
- def self.default_tags
- {
- Labkit::Correlation::CorrelationId::LOG_KEY.to_sym => Labkit::Correlation::CorrelationId.current_id,
- locale: I18n.locale
- }
- end
+ raise exception if should_raise_for_dev?
+ end
- def self.build_extra_data(exception, issue_url, extra)
- exception.try(:sentry_extra_data)&.tap do |data|
- extra.merge!(data) if data.is_a?(Hash)
+ # 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)
end
- extra.merge({ issue_url: issue_url }.compact)
- end
+ # 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
+
+ def process_exception(exception, sentry: false, logging: true, extra:)
+ exception.try(:sentry_extra_data)&.tap do |data|
+ extra = extra.merge(data) if data.is_a?(Hash)
+ end
+
+ if sentry && Raven.configuration.server
+ Raven.capture_exception(exception, tags: default_tags, extra: extra)
+ end
- private_class_method :build_extra_data
+ if logging
+ # TODO: this logic could migrate into `Gitlab::ExceptionLogFormatter`
+ # and we could also flatten deep nested hashes if required for search
+ # (e.g. if `extra` includes hash of hashes).
+ # In the current implementation, we don't flatten multi-level folded hashes.
+ log_hash = {}
+ Raven.context.tags.each { |name, value| log_hash["tags.#{name}"] = value }
+ Raven.context.user.each { |name, value| log_hash["user.#{name}"] = value }
+ Raven.context.extra.merge(extra).each { |name, value| log_hash["extra.#{name}"] = value }
+
+ Gitlab::ExceptionLogFormatter.format!(exception, log_hash)
+
+ Gitlab::Sentry::Logger.error(log_hash)
+ end
+ end
+
+ def sentry_dsn
+ return unless Rails.env.production? || Rails.env.development?
+ return unless Gitlab.config.sentry.enabled
+
+ Gitlab.config.sentry.dsn
+ end
+
+ def should_raise_for_dev?
+ Rails.env.development? || Rails.env.test?
+ end
+
+ def default_tags
+ {
+ Labkit::Correlation::CorrelationId::LOG_KEY.to_sym => Labkit::Correlation::CorrelationId.current_id,
+ locale: I18n.locale
+ }
+ end
+
+ def add_context_from_exception_type(event, hint)
+ if ActiveModel::MissingAttributeError === hint[:exception]
+ columns_hash = ActiveRecord::Base
+ .connection
+ .schema_cache
+ .instance_variable_get(:@columns_hash)
+ .map { |k, v| [k, v.map(&:first)] }
+ .to_h
+
+ event.extra.merge!(columns_hash)
+ end
+
+ event
+ end
+ end
end
end