Commit 190fa31c authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch 'qmnguyen0711/849-sentry-upgrade-sentry-raven-gem' into 'master'

Upgrade Sentry gem to version 4.4.0

See merge request gitlab-org/gitlab!62387
parents b8bfeb3b f9fef437
......@@ -301,7 +301,9 @@ gem 'gitlab-license', '~> 1.5'
gem 'rack-attack', '~> 6.3.0'
# Sentry integration
gem 'sentry-raven', '~> 3.1'
gem 'sentry-ruby', '~> 4.4.0'
gem 'sentry-sidekiq', '~> 4.4.0'
gem 'sentry-rails', '~> 4.4.0'
# PostgreSQL query parsing
#
......
......@@ -1172,8 +1172,18 @@ GEM
selenium-webdriver (3.142.7)
childprocess (>= 0.5, < 4.0)
rubyzip (>= 1.2.2)
sentry-raven (3.1.2)
sentry-rails (4.4.0)
railties (>= 5.0)
sentry-ruby-core (~> 4.4.0.pre.beta)
sentry-ruby (4.4.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
faraday (>= 1.0)
sentry-ruby-core (= 4.4.2)
sentry-ruby-core (4.4.2)
concurrent-ruby
faraday
sentry-sidekiq (4.4.0)
sentry-ruby-core (~> 4.4.0.pre.beta)
settingslogic (2.0.9)
sexp_processor (4.15.1)
shellany (0.0.1)
......@@ -1619,7 +1629,9 @@ DEPENDENCIES
sassc-rails (~> 2.1.0)
seed-fu (~> 2.3.7)
selenium-webdriver (~> 3.142)
sentry-raven (~> 3.1)
sentry-rails (~> 4.4.0)
sentry-ruby (~> 4.4.0)
sentry-sidekiq (~> 4.4.0)
settingslogic (~> 2.0.9)
shoulda-matchers (~> 4.0.1)
sidekiq (~> 5.2.7)
......
......@@ -19,22 +19,21 @@ module Gitlab
PROCESSORS = [
::Gitlab::ErrorTracking::Processor::SidekiqProcessor,
::Gitlab::ErrorTracking::Processor::GrpcErrorProcessor,
::Gitlab::ErrorTracking::Processor::ContextPayloadProcessor
::Gitlab::ErrorTracking::Processor::ContextPayloadProcessor,
# IMPORTANT: this processor must stay at the bottom, right before
# sending the event to Sentry.
::Gitlab::ErrorTracking::Processor::SanitizerProcessor
].freeze
class << self
def configure
Raven.configure do |config|
Sentry.init 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.environment = Gitlab.config.sentry.environment
config.before_send = method(:before_send)
config.background_worker_threads = 0
config.send_default_pii = true
yield config if block_given?
end
......@@ -108,8 +107,11 @@ module Gitlab
def process_exception(exception, sentry: false, logging: true, extra:)
context_payload = Gitlab::ErrorTracking::ContextPayloadGenerator.generate(exception, extra)
if sentry && Raven.configuration.server
Raven.capture_exception(exception, **context_payload)
# 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
if logging
......
......@@ -3,7 +3,7 @@
module Gitlab
module ErrorTracking
class LogFormatter
# Note: all the accesses to Raven's contexts here are to keep the
# Note: all the accesses to Sentry's contexts here are to keep the
# backward-compatibility to Sentry's built-in integrations. In future,
# they can be removed.
def generate_log(exception, context_payload)
......@@ -20,21 +20,27 @@ module Gitlab
private
def append_user_to_log!(payload, context_payload)
user_context = Raven.context.user.merge(context_payload[:user])
return if current_scope.blank?
user_context = current_scope.user.merge(context_payload[:user])
user_context.each do |key, value|
payload["user.#{key}"] = value
end
end
def append_tags_to_log!(payload, context_payload)
tags_context = Raven.context.tags.merge(context_payload[:tags])
return if current_scope.blank?
tags_context = current_scope.tags.merge(context_payload[:tags])
tags_context.each do |key, value|
payload["tags.#{key}"] = value
end
end
def append_extra_to_log!(payload, context_payload)
extra = Raven.context.extra.merge(context_payload[:extra])
return if current_scope.blank?
extra = current_scope.extra.merge(context_payload[:extra])
extra = extra.except(:server)
# The extra value for sidekiq is a hash whose keys are strings.
......@@ -50,6 +56,10 @@ module Gitlab
payload["extra.#{key}"] = value
end
end
def current_scope
Sentry.get_current_scope
end
end
end
end
......@@ -17,8 +17,7 @@ module Gitlab
# Sentry can report multiple exceptions in an event. Sanitize
# only the first one since that's what is used for grouping.
def process_first_exception_value(event)
# Better in new version, will be event.exception.values
exceptions = event.instance_variable_get(:@interfaces)[:exception]&.values
exceptions = event.exception&.instance_variable_get(:@values)
return unless exceptions.is_a?(Array)
......@@ -33,9 +32,7 @@ module Gitlab
message, debug_str = split_debug_error_string(raw_message)
# Worse in new version, no setter! Have to poke at the
# instance variable
exception.value = message if message
exception.instance_variable_set(:@value, message) if message
event.extra[:grpc_debug_error_string] = debug_str if debug_str
end
......@@ -66,7 +63,7 @@ module Gitlab
def valid_exception?(exception)
case exception
when Raven::SingleExceptionInterface
when Sentry::SingleExceptionInterface
exception&.value
else
false
......
# frozen_string_literal: true
module Gitlab
module ErrorTracking
module Processor
module SanitizerProcessor
SANITIZED_HTTP_HEADERS = %w[Authorization Private-Token].freeze
SANITIZED_ATTRIBUTES = %i[user contexts extra tags].freeze
# This processor removes sensitive fields or headers from the event
# before sending. Sentry versions above 4.0 don't support
# sanitized_fields and sanitized_http_headers anymore. The official
# document recommends using before_send instead.
#
# For more information, please visit:
# https://docs.sentry.io/platforms/ruby/guides/rails/configuration/filtering/#using-beforesend
def self.call(event)
if event.request.present? && event.request.headers.is_a?(Hash)
header_filter = ActiveSupport::ParameterFilter.new(SANITIZED_HTTP_HEADERS)
event.request.headers = header_filter.filter(event.request.headers)
end
attribute_filter = ActiveSupport::ParameterFilter.new(Rails.application.config.filter_parameters)
SANITIZED_ATTRIBUTES.each do |attribute|
event.send("#{attribute}=", attribute_filter.filter(event.send(attribute))) # rubocop:disable GitlabSecurity/PublicSend
end
event
end
end
end
end
end
# frozen_string_literal: true
require 'fast_spec_helper'
require 'spec_helper'
require 'rspec-parameterized'
RSpec.describe Gitlab::ErrorTracking::ContextPayloadGenerator do
......
......@@ -27,9 +27,9 @@ RSpec.describe Gitlab::ErrorTracking::LogFormatter do
end
before do
Raven.context.user[:user_flag] = 'flag'
Raven.context.tags[:shard] = 'catchall'
Raven.context.extra[:some_info] = 'info'
Sentry.set_user(user_flag: 'flag')
Sentry.set_tags(shard: 'catchall')
Sentry.set_extras(some_info: 'info')
allow(exception).to receive(:backtrace).and_return(
[
......@@ -40,7 +40,7 @@ RSpec.describe Gitlab::ErrorTracking::LogFormatter do
end
after do
::Raven::Context.clear!
::Sentry.get_current_scope.clear
end
it 'appends error-related log fields and filters sensitive Sidekiq arguments' do
......
......@@ -4,18 +4,14 @@ require 'spec_helper'
RSpec.describe Gitlab::ErrorTracking::Processor::ContextPayloadProcessor do
describe '.call' do
let(:required_options) do
{
configuration: Raven.configuration,
context: Raven.context,
breadcrumbs: Raven.breadcrumbs
}
end
let(:event) { Raven::Event.new(required_options.merge(payload)) }
let(:exception) { StandardError.new('Test exception') }
let(:event) { Sentry.get_current_client.event_from_exception(exception) }
let(:result_hash) { described_class.call(event).to_hash }
before do
Sentry.get_current_scope.update_from_options(**payload)
Sentry.get_current_scope.apply_to_event(event)
allow_next_instance_of(Gitlab::ErrorTracking::ContextPayloadGenerator) do |generator|
allow(generator).to receive(:generate).and_return(
user: { username: 'root' },
......@@ -25,6 +21,10 @@ RSpec.describe Gitlab::ErrorTracking::Processor::ContextPayloadProcessor do
end
end
after do
Sentry.get_current_scope.clear
end
let(:payload) do
{
user: { ip_address: '127.0.0.1' },
......
......@@ -4,16 +4,17 @@ require 'spec_helper'
RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
describe '.call' do
let(:required_options) do
{
configuration: Raven.configuration,
context: Raven.context,
breadcrumbs: Raven.breadcrumbs
}
let(:event) { Sentry.get_current_client.event_from_exception(exception) }
let(:result_hash) { described_class.call(event).to_hash }
before do
Sentry.get_current_scope.update_from_options(**data)
Sentry.get_current_scope.apply_to_event(event)
end
let(:event) { Raven::Event.from_exception(exception, required_options.merge(data)) }
let(:result_hash) { described_class.call(event).to_hash }
after do
Sentry.get_current_scope.clear
end
context 'when there is no GRPC exception' do
let(:exception) { RuntimeError.new }
......@@ -56,7 +57,7 @@ RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
end
it 'removes the debug error string and stores it as an extra field' do
expect(result_hash).not_to include(:fingerprint)
expect(result_hash[:fingerprint]).to be_empty
expect(result_hash[:exception][:values].first)
.to include(type: 'GRPC::DeadlineExceeded', value: '4:Deadline Exceeded.')
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::ErrorTracking::Processor::SanitizerProcessor do
describe '.call' do
let(:event) { Sentry.get_current_client.event_from_exception(exception) }
let(:result_hash) { described_class.call(event).to_hash }
before do
data.each do |key, value|
event.send("#{key}=", value)
end
end
after do
Sentry.get_current_scope.clear
end
context 'when event attributes contains sensitive information' do
let(:exception) { RuntimeError.new }
let(:data) do
{
contexts: {
jwt: 'abcdef',
controller: 'GraphController#execute'
},
tags: {
variables: %w[some sensitive information'],
deep_hash: {
sharedSecret: 'secret123'
}
},
user: {
email: 'a@a.com',
password: 'nobodyknows'
},
extra: {
issue_url: 'http://gitlab.com/gitlab-org/gitlab-foss/-/issues/1',
my_token: '[FILTERED]',
another_token: '[FILTERED]'
}
}
end
it 'filters sensitive attributes' do
expect_next_instance_of(ActiveSupport::ParameterFilter) do |instance|
expect(instance).to receive(:filter).exactly(4).times.and_call_original
end
expect(result_hash).to include(
contexts: {
jwt: '[FILTERED]',
controller: 'GraphController#execute'
},
tags: {
variables: '[FILTERED]',
deep_hash: {
sharedSecret: '[FILTERED]'
}
},
user: {
email: 'a@a.com',
password: '[FILTERED]'
},
extra: {
issue_url: 'http://gitlab.com/gitlab-org/gitlab-foss/-/issues/1',
my_token: '[FILTERED]',
another_token: '[FILTERED]'
}
)
end
end
context 'when request headers contains sensitive information' do
let(:exception) { RuntimeError.new }
let(:data) { {} }
before do
event.rack_env = {
'HTTP_AUTHORIZATION' => 'Bearer 123456',
'HTTP_PRIVATE_TOKEN' => 'abcdef',
'HTTP_GITLAB_WORKHORSE_PROXY_START' => 123456
}
end
it 'filters sensitive headers' do
expect(result_hash[:request][:headers]).to include(
'Authorization' => '[FILTERED]',
'Private-Token' => '[FILTERED]',
'Gitlab-Workhorse-Proxy-Start' => '123456'
)
end
end
end
end
......@@ -95,16 +95,18 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do
end
describe '.call' do
let(:required_options) do
{
configuration: Raven.configuration,
context: Raven.context,
breadcrumbs: Raven.breadcrumbs
}
let(:exception) { StandardError.new('Test exception') }
let(:event) { Sentry.get_current_client.event_from_exception(exception) }
let(:result_hash) { described_class.call(event).to_hash }
before do
Sentry.get_current_scope.update_from_options(**wrapped_value)
Sentry.get_current_scope.apply_to_event(event)
end
let(:event) { Raven::Event.new(required_options.merge(wrapped_value)) }
let(:result_hash) { described_class.call(event).to_hash }
after do
Sentry.get_current_scope.clear
end
context 'when there is Sidekiq data' do
let(:wrapped_value) { { extra: { sidekiq: value } } }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
require 'raven/transports/dummy'
require 'sentry/transport/dummy_transport'
RSpec.describe Gitlab::ErrorTracking do
let(:exception) { RuntimeError.new('boom') }
......@@ -43,7 +43,7 @@ RSpec.describe Gitlab::ErrorTracking do
}
end
let(:sentry_event) { Gitlab::Json.parse(Raven.client.transport.events.last[1]) }
let(:sentry_event) { Sentry.get_current_client.transport.events.last }
before do
stub_sentry_settings
......@@ -53,7 +53,7 @@ RSpec.describe Gitlab::ErrorTracking do
allow(I18n).to receive(:locale).and_return('en')
described_class.configure do |config|
config.encoding = 'json'
config.transport.transport_class = Sentry::DummyTransport
end
end
......@@ -63,6 +63,10 @@ RSpec.describe Gitlab::ErrorTracking do
end
end
after do
::Sentry.get_current_scope.clear
end
describe '.track_and_raise_for_dev_exception' do
context 'when exceptions for dev should be raised' do
before do
......@@ -70,7 +74,7 @@ RSpec.describe Gitlab::ErrorTracking do
end
it 'raises the exception' do
expect(Raven).to receive(:capture_exception).with(exception, sentry_payload)
expect(Sentry).to receive(:capture_exception).with(exception, sentry_payload)
expect do
described_class.track_and_raise_for_dev_exception(
......@@ -88,7 +92,7 @@ RSpec.describe Gitlab::ErrorTracking do
end
it 'logs the exception with all attributes passed' do
expect(Raven).to receive(:capture_exception).with(exception, sentry_payload)
expect(Sentry).to receive(:capture_exception).with(exception, sentry_payload)
described_class.track_and_raise_for_dev_exception(
exception,
......@@ -111,7 +115,7 @@ RSpec.describe Gitlab::ErrorTracking do
describe '.track_and_raise_exception' do
it 'always raises the exception' do
expect(Raven).to receive(:capture_exception).with(exception, sentry_payload)
expect(Sentry).to receive(:capture_exception).with(exception, sentry_payload)
expect do
described_class.track_and_raise_for_dev_exception(
......@@ -139,14 +143,14 @@ RSpec.describe Gitlab::ErrorTracking do
subject(:track_exception) { described_class.track_exception(exception, extra) }
before do
allow(Raven).to receive(:capture_exception).and_call_original
allow(Sentry).to receive(:capture_exception).and_call_original
allow(Gitlab::ErrorTracking::Logger).to receive(:error)
end
it 'calls Raven.capture_exception' do
it 'calls Sentry.capture_exception' do
track_exception
expect(Raven).to have_received(:capture_exception).with(
expect(Sentry).to have_received(:capture_exception).with(
exception,
sentry_payload
)
......@@ -172,25 +176,31 @@ RSpec.describe Gitlab::ErrorTracking do
context 'the exception implements :sentry_extra_data' do
let(:extra_info) { { event: 'explosion', size: :massive } }
let(:exception) { double(message: 'bang!', sentry_extra_data: extra_info, backtrace: caller, cause: nil) }
before do
allow(exception).to receive(:sentry_extra_data).and_return(extra_info)
end
it 'includes the extra data from the exception in the tracking information' do
track_exception
expect(Raven).to have_received(:capture_exception).with(
expect(Sentry).to have_received(:capture_exception).with(
exception, a_hash_including(extra: a_hash_including(extra_info))
)
end
end
context 'the exception implements :sentry_extra_data, which returns nil' do
let(:exception) { double(message: 'bang!', sentry_extra_data: nil, backtrace: caller, cause: nil) }
let(:extra) { { issue_url: issue_url } }
before do
allow(exception).to receive(:sentry_extra_data).and_return(nil)
end
it 'just includes the other extra info' do
track_exception
expect(Raven).to have_received(:capture_exception).with(
expect(Sentry).to have_received(:capture_exception).with(
exception, a_hash_including(extra: a_hash_including(extra))
)
end
......@@ -202,7 +212,7 @@ RSpec.describe Gitlab::ErrorTracking do
it 'injects the normalized sql query into extra' do
track_exception
expect(sentry_event.dig('extra', 'sql')).to eq('SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1')
expect(sentry_event.extra[:sql]).to eq('SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1')
end
end
......@@ -212,7 +222,7 @@ RSpec.describe Gitlab::ErrorTracking do
track_exception
expect(sentry_event.dig('extra', 'sql')).to eq('SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1')
expect(sentry_event.extra[:sql]).to eq('SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1')
end
end
end
......@@ -221,27 +231,27 @@ RSpec.describe Gitlab::ErrorTracking do
subject(:track_exception) { described_class.track_exception(exception, extra) }
before do
allow(Raven).to receive(:capture_exception).and_call_original
allow(Sentry).to receive(:capture_exception).and_call_original
allow(Gitlab::ErrorTracking::Logger).to receive(:error)
end
context 'custom GitLab context when using Raven.capture_exception directly' do
subject(:raven_capture_exception) { Raven.capture_exception(exception) }
context 'custom GitLab context when using Sentry.capture_exception directly' do
subject(:track_exception) { Sentry.capture_exception(exception) }
it 'merges a default set of tags into the existing tags' do
allow(Raven.context).to receive(:tags).and_return(foo: 'bar')
Sentry.set_tags(foo: 'bar')
raven_capture_exception
track_exception
expect(sentry_event['tags']).to include('correlation_id', 'feature_category', 'foo', 'locale', 'program')
expect(sentry_event.tags).to include(:correlation_id, :feature_category, :foo, :locale, :program)
end
it 'merges the current user information into the existing user information' do
Raven.user_context(id: -1)
Sentry.set_user(id: -1)
raven_capture_exception
track_exception
expect(sentry_event['user']).to eq('id' => -1, 'username' => user.username)
expect(sentry_event.user).to eq(id: -1, username: user.username)
end
end
......@@ -265,7 +275,7 @@ RSpec.describe Gitlab::ErrorTracking do
it 'does not filter parameters when sending to Sentry' do
track_exception
expect(sentry_event.dig('extra', 'sidekiq', 'args')).to eq([1, { 'id' => 2, 'name' => 'hello' }, 'some-value', 'another-value'])
expect(sentry_event.extra[:sidekiq]['args']).to eq([1, { 'id' => 2, 'name' => 'hello' }, 'some-value', 'another-value'])
end
end
......@@ -275,7 +285,7 @@ RSpec.describe Gitlab::ErrorTracking do
it 'filters sensitive arguments before sending and logging' do
track_exception
expect(sentry_event.dig('extra', 'sidekiq', 'args')).to eq(['[FILTERED]', 1, 2])
expect(sentry_event.extra[:sidekiq]['args']).to eq(['[FILTERED]', 1, 2])
expect(Gitlab::ErrorTracking::Logger).to have_received(:error).with(
hash_including(
'extra.sidekiq' => {
......@@ -295,8 +305,8 @@ RSpec.describe Gitlab::ErrorTracking do
it 'sets the GRPC debug error string in the Sentry event and adds a custom fingerprint' do
track_exception
expect(sentry_event.dig('extra', 'grpc_debug_error_string')).to eq('{"hello":1}')
expect(sentry_event['fingerprint']).to eq(['GRPC::DeadlineExceeded', '4:unknown cause.'])
expect(sentry_event.extra[:grpc_debug_error_string]).to eq('{"hello":1}')
expect(sentry_event.fingerprint).to eq(['GRPC::DeadlineExceeded', '4:unknown cause.'])
end
end
......@@ -306,8 +316,8 @@ RSpec.describe Gitlab::ErrorTracking do
it 'does not do any processing on the event' do
track_exception
expect(sentry_event['extra']).not_to include('grpc_debug_error_string')
expect(sentry_event['fingerprint']).to eq(['GRPC::DeadlineExceeded', '4:unknown cause'])
expect(sentry_event.extra).not_to include(:grpc_debug_error_string)
expect(sentry_event.fingerprint).to eq(['GRPC::DeadlineExceeded', '4:unknown cause'])
end
end
end
......
# frozen_string_literal: true
require 'spec_helper'
require 'raven/transports/dummy'
require 'sentry/transport/dummy_transport'
require_relative '../../../config/initializers/sentry'
RSpec.describe API::Helpers do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment