Commit 5bd6edfb authored by Andrejs Cunskis's avatar Andrejs Cunskis

Merge branch 'browser_log_urls' into 'master'

Add Browser level test Sentry and Kibana logs when 500 error is found

See merge request gitlab-org/gitlab!82209
parents 1d07634f b2a93504
......@@ -54,7 +54,7 @@ module QA
body)
unless response.code == HTTP_STATUS_OK
raise ResourceFabricationFailedError, "Updating #{self.class.name} using the API failed (#{response.code}) with `#{response}`."
raise ResourceFabricationFailedError, "Updating #{self.class.name} using the API failed (#{response.code}) with `#{response}`.\n#{QA::Support::Loglinking.failure_metadata(response.headers[:x_request_id])}"
end
process_api_response(parse_body(response))
......@@ -91,9 +91,9 @@ module QA
response = get(request.url)
if response.code == HTTP_STATUS_SERVER_ERROR
raise InternalServerError, "Failed to GET #{request.mask_url} - (#{response.code}): `#{response}`."
raise InternalServerError, "Failed to GET #{request.mask_url} - (#{response.code}): `#{response}`.\n#{QA::Support::Loglinking.failure_metadata(response.headers[:x_request_id])}"
elsif response.code != HTTP_STATUS_OK
raise ResourceNotFoundError, "Resource at #{request.mask_url} could not be found (#{response.code}): `#{response}`."
raise ResourceNotFoundError, "Resource at #{request.mask_url} could not be found (#{response.code}): `#{response}`.\n#{QA::Support::Loglinking.failure_metadata(response.headers[:x_request_id])}"
end
@api_fabrication_http_method = :get # rubocop:disable Gitlab/ModuleWithInstanceVariables
......@@ -114,6 +114,7 @@ module QA
unless graphql_response.code == HTTP_STATUS_OK && (body[:errors].nil? || body[:errors].empty?)
raise(ResourceFabricationFailedError, <<~MSG)
Fabrication of #{self.class.name} using the API failed (#{graphql_response.code}) with `#{graphql_response}`.
#{QA::Support::Loglinking.failure_metadata(graphql_response.headers[:x_request_id])}
MSG
end
......@@ -126,7 +127,7 @@ module QA
unless response.code == HTTP_STATUS_CREATED
raise(
ResourceFabricationFailedError,
"Fabrication of #{self.class.name} using the API failed (#{response.code}) with `#{response}`."
"Fabrication of #{self.class.name} using the API failed (#{response.code}) with `#{response}`.\n#{QA::Support::Loglinking.failure_metadata(response.headers[:x_request_id])}"
)
end
......@@ -145,7 +146,7 @@ module QA
response = delete(request.url)
unless [HTTP_STATUS_NO_CONTENT, HTTP_STATUS_ACCEPTED].include? response.code
raise ResourceNotDeletedError, "Resource at #{request.mask_url} could not be deleted (#{response.code}): `#{response}`."
raise ResourceNotDeletedError, "Resource at #{request.mask_url} could not be deleted (#{response.code}): `#{response}`.\n#{QA::Support::Loglinking.failure_metadata(response.headers[:x_request_id])}"
end
response
......
# frozen_string_literal: true
module QA
module Support
module Loglinking
# Static address variables declared for mapping environment to logging URLs
STAGING_ADDRESS = 'https://staging.gitlab.com'
STAGING_REF_ADDRESS = 'https://staging-ref.gitlab.com'
PRODUCTION_ADDRESS = 'https://www.gitlab.com'
PRE_PROD_ADDRESS = 'https://pre.gitlab.com'
SENTRY_ENVIRONMENTS = {
staging: 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg',
staging_canary: 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg-cny',
staging_ref: 'https://sentry.gitlab.net/gitlab/staging-ref/?environment=gstg-ref',
pre: 'https://sentry.gitlab.net/gitlab/pregitlabcom/?environment=pre',
canary: 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd',
production: 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd-cny'
}.freeze
KIBANA_ENVIRONMENTS = {
staging: 'https://nonprod-log.gitlab.net/',
staging_canary: 'https://nonprod-log.gitlab.net/',
canary: 'https://log.gprd.gitlab.net/',
production: 'https://log.gprd.gitlab.net/'
}.freeze
def self.failure_metadata(correlation_id)
return if correlation_id.blank?
sentry_uri = sentry_url
kibana_uri = kibana_url
errors = ["Correlation Id: #{correlation_id}"]
errors << "Sentry Url: #{sentry_uri}&query=correlation_id%3A%22#{correlation_id}%22" if sentry_uri
errors << "Kibana Url: #{kibana_uri}app/discover#/?_a=(query:(language:kuery,query:'json.correlation_id%20:%20#{correlation_id}'))" if kibana_uri
errors.join("\n")
end
def self.sentry_url
return unless logging_environment?
SENTRY_ENVIRONMENTS[logging_environment]
end
def self.kibana_url
return unless logging_environment?
KIBANA_ENVIRONMENTS[logging_environment]
end
def self.logging_environment
address = QA::Runtime::Scenario.attributes[:gitlab_address]
return if address.nil?
case address
when STAGING_ADDRESS
canary? ? :staging_canary : :staging
when STAGING_REF_ADDRESS
:staging_ref
when PRODUCTION_ADDRESS
canary? ? :canary : :production
when PRE_PROD_ADDRESS
:pre
else
nil
end
end
def self.logging_environment?
!logging_environment.nil?
end
def self.cookies
browser_cookies = Capybara.current_session.driver.browser.manage.all_cookies
# rubocop:disable Rails/IndexBy
browser_cookies.each_with_object({}) do |cookie, memo|
memo[cookie[:name]] = cookie
end
# rubocop:enable Rails/IndexBy
end
def self.canary?
cookies.dig('gitlab_canary', :value) == 'true'
end
end
end
end
......@@ -5,14 +5,28 @@ module QA
class PageErrorChecker
class << self
def report!(page, error_code)
request_id_string = ''
if error_code == 500
request_id = parse_five_c_page_request_id(page)
if request_id
request_id_string = "\n\n" + Loglinking.failure_metadata(request_id)
end
end
report = if QA::Runtime::Env.browser == :chrome
return_chrome_errors(page, error_code)
else
status_code_report(error_code)
end
raise "#{report}\n\n"\
"Path: #{page.current_path}"
raise "Error Code #{error_code}\n\n"\
"#{report}\n\n"\
"Path: #{page.current_path}"\
"#{request_id_string}"
end
def parse_five_c_page_request_id(page)
Nokogiri::HTML.parse(page.html).xpath("/html/body/div/p[1]/code").children.first
end
def return_chrome_errors(page, error_code)
......
......@@ -108,15 +108,58 @@ RSpec.describe QA::Resource::ApiFabricator do
context 'when the POST fails' do
let(:post_response) { { error: "Name already taken." } }
let(:raw_post) { double('Raw POST response', code: 400, body: post_response.to_json) }
let(:raw_post) { double('Raw POST response', code: 400, body: post_response.to_json, headers: {}) }
it 'raises a ResourceFabricationFailedError exception' do
expect(api_request).to receive(:new).with(api_client_instance, subject.api_post_path).and_return(double(url: resource_web_url))
expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post)
allow(QA::Support::Loglinking).to receive(:logging_environment).and_return(nil)
expect { subject.fabricate_via_api! }.to raise_error(described_class::ResourceFabricationFailedError, "Fabrication of FooBarResource using the API failed (400) with `#{raw_post}`.")
expect { subject.fabricate_via_api! }.to raise_error do |error|
expect(error.class).to eql(described_class::ResourceFabricationFailedError)
expect(error.to_s).to eql(<<~ERROR.chomp)
Fabrication of FooBarResource using the API failed (400) with `#{raw_post}`.\n
ERROR
end
expect(subject.api_resource).to be_nil
end
it 'logs a correlation id' do
response = double('Raw POST response', code: 400, body: post_response.to_json, headers: { x_request_id: 'foobar' })
allow(QA::Support::Loglinking).to receive(:logging_environment).and_return(nil)
expect(api_request).to receive(:new).with(api_client_instance, subject.api_post_path).and_return(double(url: resource_web_url))
expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(response)
expect { subject.fabricate_via_api! }.to raise_error do |error|
expect(error.class).to eql(described_class::ResourceFabricationFailedError)
expect(error.to_s).to eql(<<~ERROR.chomp)
Fabrication of FooBarResource using the API failed (400) with `#{raw_post}`.
Correlation Id: foobar
ERROR
end
end
it 'logs a sentry url from staging' do
response = double('Raw POST response', code: 400, body: post_response.to_json, headers: { x_request_id: 'foobar' })
cookies = [{ name: 'Foo', value: 'Bar' }, { name: 'gitlab_canary', value: 'true' }]
allow(Capybara.current_session).to receive_message_chain(:driver, :browser, :manage, :all_cookies).and_return(cookies)
allow(QA::Runtime::Scenario).to receive(:attributes).and_return({ gitlab_address: 'https://staging.gitlab.com' })
expect(api_request).to receive(:new).with(api_client_instance, subject.api_post_path).and_return(double(url: resource_web_url))
expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(response)
expect { subject.fabricate_via_api! }.to raise_error do |error|
expect(error.class).to eql(described_class::ResourceFabricationFailedError)
expect(error.to_s).to eql(<<~ERROR.chomp)
Fabrication of FooBarResource using the API failed (400) with `#{raw_post}`.
Correlation Id: foobar
Sentry Url: https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg-cny&query=correlation_id%3A%22foobar%22
Kibana Url: https://nonprod-log.gitlab.net/app/discover#/?_a=(query:(language:kuery,query:'json.correlation_id%20:%20foobar'))
ERROR
end
end
end
end
......
# frozen_string_literal: true
RSpec.describe QA::Support::Loglinking do
describe '.failure_metadata' do
context 'return nil string' do
it 'if correlation_id is empty' do
expect(QA::Support::Loglinking.failure_metadata('')).to eq(nil)
end
it 'if correlation_id is nil' do
expect(QA::Support::Loglinking.failure_metadata(nil)).to eq(nil)
end
end
context 'return error string' do
it 'with sentry URL' do
allow(QA::Support::Loglinking).to receive(:sentry_url).and_return('https://sentry.address/?environment=bar')
allow(QA::Support::Loglinking).to receive(:kibana_url).and_return(nil)
expect(QA::Support::Loglinking.failure_metadata('foo123')).to eql(<<~ERROR.chomp)
Correlation Id: foo123
Sentry Url: https://sentry.address/?environment=bar&query=correlation_id%3A%22foo123%22
ERROR
end
it 'with kibana URL' do
allow(QA::Support::Loglinking).to receive(:sentry_url).and_return(nil)
allow(QA::Support::Loglinking).to receive(:kibana_url).and_return('https://kibana.address/')
expect(QA::Support::Loglinking.failure_metadata('foo123')).to eql(<<~ERROR.chomp)
Correlation Id: foo123
Kibana Url: https://kibana.address/app/discover#/?_a=(query:(language:kuery,query:'json.correlation_id%20:%20foo123'))
ERROR
end
end
end
describe '.sentry_url' do
let(:url_hash) do
{
:staging => 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg',
:staging_canary => 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg-cny',
:staging_ref => 'https://sentry.gitlab.net/gitlab/staging-ref/?environment=gstg-ref',
:pre => 'https://sentry.gitlab.net/gitlab/pregitlabcom/?environment=pre',
:canary => 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd',
:production => 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd-cny',
:foo => nil,
nil => nil
}
end
it 'returns sentry URL if environment found' do
url_hash.each do |environment, url|
allow(QA::Support::Loglinking).to receive(:logging_environment).and_return(environment)
expect(QA::Support::Loglinking.sentry_url).to eq(url)
end
end
end
describe '.kibana_url' do
let(:url_hash) do
{
:staging => 'https://nonprod-log.gitlab.net/',
:staging_canary => 'https://nonprod-log.gitlab.net/',
:staging_ref => nil,
:pre => nil,
:canary => 'https://log.gprd.gitlab.net/',
:production => 'https://log.gprd.gitlab.net/',
:foo => nil,
nil => nil
}
end
it 'returns kibana URL if environment found' do
url_hash.each do |environment, url|
allow(QA::Support::Loglinking).to receive(:logging_environment).and_return(environment)
expect(QA::Support::Loglinking.kibana_url).to eq(url)
end
end
end
describe '.logging_environment' do
let(:staging_address) { 'https://staging.gitlab.com' }
let(:staging_ref_address) { 'https://staging-ref.gitlab.com' }
let(:production_address) { 'https://www.gitlab.com' }
let(:pre_prod_address) { 'https://pre.gitlab.com' }
let(:logging_env_array) do
[
{
address: staging_address,
canary: false,
expected_env: :staging
},
{
address: staging_address,
canary: true,
expected_env: :staging_canary
},
{
address: staging_ref_address,
canary: true,
expected_env: :staging_ref
},
{
address: production_address,
canary: false,
expected_env: :production
},
{
address: production_address,
canary: true,
expected_env: :canary
},
{
address: pre_prod_address,
canary: true,
expected_env: :pre
},
{
address: 'https://foo.com',
canary: true,
expected_env: nil
}
]
end
it 'returns logging environment if environment found' do
logging_env_array.each do |logging_env_hash|
allow(QA::Runtime::Scenario).to receive(:attributes).and_return({ gitlab_address: logging_env_hash[:address] })
allow(QA::Support::Loglinking).to receive(:canary?).and_return(logging_env_hash[:canary])
expect(QA::Support::Loglinking.logging_environment).to eq(logging_env_hash[:expected_env])
end
end
end
describe '.logging_environment?' do
context 'returns boolean' do
it 'returns true if logging_environment is not nil' do
allow(QA::Support::Loglinking).to receive(:logging_environment).and_return(:staging)
expect(QA::Support::Loglinking.logging_environment?).to eq(true)
end
it 'returns false if logging_environment is nil' do
allow(QA::Support::Loglinking).to receive(:logging_environment).and_return(nil)
expect(QA::Support::Loglinking.logging_environment?).to eq(false)
end
end
end
describe '.cookies' do
let(:cookies) { [{ name: 'Foo', value: 'Bar' }, { name: 'gitlab_canary', value: 'true' }] }
it 'returns browser cookies' do
allow(Capybara.current_session).to receive_message_chain(:driver, :browser, :manage, :all_cookies).and_return(cookies)
expect(QA::Support::Loglinking.cookies).to eq({ "Foo" => { name: "Foo", value: "Bar" }, "gitlab_canary" => { name: "gitlab_canary", value: "true" } })
end
end
describe '.canary?' do
context 'gitlab_canary cookie is present' do
it 'and true returns true' do
allow(QA::Support::Loglinking).to receive(:cookies).and_return({ 'gitlab_canary' => { name: 'gitlab_canary', value: 'true' } })
expect(QA::Support::Loglinking.canary?).to eq(true)
end
it 'and not true returns false' do
allow(QA::Support::Loglinking).to receive(:cookies).and_return({ 'gitlab_canary' => { name: 'gitlab_canary', value: 'false' } })
expect(QA::Support::Loglinking.canary?).to eq(false)
end
end
context 'gitlab_canary cookie is not present' do
it 'returns false' do
allow(QA::Support::Loglinking).to receive(:cookies).and_return({ 'foo' => { name: 'foo', path: '/pathname' } })
expect(QA::Support::Loglinking.canary?).to eq(false)
end
end
end
end
......@@ -8,16 +8,28 @@ RSpec.describe QA::Support::PageErrorChecker do
describe '.report!' do
context 'reports errors' do
let(:expected_chrome_error) do
"Error Code 500\n\n"\
"chrome errors\n\n"\
"Path: #{test_path}"
"Path: #{test_path}\n\n"\
"Logging: foo123"
end
let(:expected_basic_error) do
"Error Code 500\n\n"\
"foo status\n\n"\
"Path: #{test_path}\n\n"\
"Logging: foo123"
end
let(:expected_basic_404) do
"Error Code 404\n\n"\
"foo status\n\n"\
"Path: #{test_path}"
end
it 'reports error message on chrome browser' do
allow(QA::Support::PageErrorChecker).to receive(:parse_five_c_page_request_id).and_return('foo123')
allow(QA::Support::Loglinking).to receive(:failure_metadata).with('foo123').and_return('Logging: foo123')
allow(QA::Support::PageErrorChecker).to receive(:return_chrome_errors).and_return('chrome errors')
allow(page).to receive(:current_path).and_return(test_path)
allow(QA::Runtime::Env).to receive(:browser).and_return(:chrome)
......@@ -26,12 +38,64 @@ RSpec.describe QA::Support::PageErrorChecker do
end
it 'reports basic message on non-chrome browser' do
allow(QA::Support::PageErrorChecker).to receive(:parse_five_c_page_request_id).and_return('foo123')
allow(QA::Support::Loglinking).to receive(:failure_metadata).with('foo123').and_return('Logging: foo123')
allow(QA::Support::PageErrorChecker).to receive(:status_code_report).and_return('foo status')
allow(page).to receive(:current_path).and_return(test_path)
allow(QA::Runtime::Env).to receive(:browser).and_return(:firefox)
expect { QA::Support::PageErrorChecker.report!(page, 500) }.to raise_error(RuntimeError, expected_basic_error)
end
it 'does not report failure metadata on non 500 error' do
allow(QA::Support::PageErrorChecker).to receive(:parse_five_c_page_request_id).and_return('foo123')
expect(QA::Support::Loglinking).not_to receive(:failure_metadata)
allow(QA::Support::PageErrorChecker).to receive(:status_code_report).and_return('foo status')
allow(page).to receive(:current_path).and_return(test_path)
allow(QA::Runtime::Env).to receive(:browser).and_return(:firefox)
expect { QA::Support::PageErrorChecker.report!(page, 404) }.to raise_error(RuntimeError, expected_basic_404)
end
end
end
describe '.parse_five_c_page_request_id' do
context 'parse correlation ID' do
require 'nokogiri'
before do
nokogiri_parse = Class.new do
def self.parse(str)
Nokogiri::HTML.parse(str)
end
end
stub_const('NokogiriParse', nokogiri_parse)
end
let(:error_500_str) do
"<html><body><div><p><code>"\
"req678"\
"</code></p></div></body></html>"
end
let(:error_500_no_code_str) do
"<html><body>"\
"The code you are looking for is not here"\
"</body></html>"
end
it 'returns code is present' do
allow(page).to receive(:html).and_return(error_500_str)
allow(Nokogiri::HTML).to receive(:parse).with(error_500_str).and_return(NokogiriParse.parse(error_500_str))
expect(QA::Support::PageErrorChecker.parse_five_c_page_request_id(page).to_str).to eq('req678')
end
it 'returns nil if not present' do
allow(page).to receive(:html).and_return(error_500_no_code_str)
allow(Nokogiri::HTML).to receive(:parse).with(error_500_no_code_str).and_return(NokogiriParse.parse(error_500_no_code_str))
expect(QA::Support::PageErrorChecker.parse_five_c_page_request_id(page)).to be_nil
end
end
end
......
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