Commit e08f014c authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch 'qmnguyen0711/1083-some-groups-error-rate-is-suspiciously-flat-2' into 'master'

Fix unknown feature categorization

See merge request gitlab-org/gitlab!63368
parents bbce77eb a52539d1
...@@ -263,7 +263,6 @@ class ApplicationController < ActionController::Base ...@@ -263,7 +263,6 @@ class ApplicationController < ActionController::Base
headers['X-XSS-Protection'] = '1; mode=block' headers['X-XSS-Protection'] = '1; mode=block'
headers['X-UA-Compatible'] = 'IE=edge' headers['X-UA-Compatible'] = 'IE=edge'
headers['X-Content-Type-Options'] = 'nosniff' headers['X-Content-Type-Options'] = 'nosniff'
headers[Gitlab::Metrics::RequestsRackMiddleware::FEATURE_CATEGORY_HEADER] = feature_category
end end
def default_cache_headers def default_cache_headers
...@@ -456,17 +455,17 @@ class ApplicationController < ActionController::Base ...@@ -456,17 +455,17 @@ class ApplicationController < ActionController::Base
end end
def set_current_context(&block) def set_current_context(&block)
Gitlab::ApplicationContext.with_context( Gitlab::ApplicationContext.push(
user: -> { context_user }, user: -> { context_user },
project: -> { @project if @project&.persisted? }, project: -> { @project if @project&.persisted? },
namespace: -> { @group if @group&.persisted? }, namespace: -> { @group if @group&.persisted? },
caller_id: caller_id, caller_id: caller_id,
remote_ip: request.ip, remote_ip: request.ip,
feature_category: feature_category) do feature_category: feature_category
yield )
ensure yield
@current_context = Gitlab::ApplicationContext.current ensure
end @current_context = Gitlab::ApplicationContext.current
end end
def set_locale(&block) def set_locale(&block)
......
...@@ -17,4 +17,4 @@ unless Rails::Configuration::MiddlewareStackProxy.method_defined?(:move) ...@@ -17,4 +17,4 @@ unless Rails::Configuration::MiddlewareStackProxy.method_defined?(:move)
end end
Rails.application.config.middleware.move(1, ActionDispatch::RequestId) Rails.application.config.middleware.move(1, ActionDispatch::RequestId)
Rails.application.config.middleware.insert_after(ActionDispatch::RequestId, Labkit::Middleware::Rack) Rails.application.config.middleware.insert(1, Labkit::Middleware::Rack)
...@@ -27,7 +27,7 @@ end ...@@ -27,7 +27,7 @@ end
Gitlab::Application.configure do |config| Gitlab::Application.configure do |config|
# 0 should be Sentry to catch errors in this middleware # 0 should be Sentry to catch errors in this middleware
config.middleware.insert(1, Gitlab::Metrics::RequestsRackMiddleware) config.middleware.insert_after(Labkit::Middleware::Rack, Gitlab::Metrics::RequestsRackMiddleware)
end end
Sidekiq.configure_server do |config| Sidekiq.configure_server do |config|
......
...@@ -52,8 +52,6 @@ module API ...@@ -52,8 +52,6 @@ module API
api_endpoint = env['api.endpoint'] api_endpoint = env['api.endpoint']
feature_category = api_endpoint.options[:for].try(:feature_category_for_app, api_endpoint).to_s feature_category = api_endpoint.options[:for].try(:feature_category_for_app, api_endpoint).to_s
header[Gitlab::Metrics::RequestsRackMiddleware::FEATURE_CATEGORY_HEADER] = feature_category
Gitlab::ApplicationContext.push( Gitlab::ApplicationContext.push(
user: -> { @current_user }, user: -> { @current_user },
project: -> { @project }, project: -> { @project },
......
...@@ -487,9 +487,8 @@ module API ...@@ -487,9 +487,8 @@ module API
def handle_api_exception(exception) def handle_api_exception(exception)
if report_exception?(exception) if report_exception?(exception)
define_params_for_grape_middleware define_params_for_grape_middleware
Gitlab::ApplicationContext.with_context(user: current_user) do Gitlab::ApplicationContext.push(user: current_user)
Gitlab::ErrorTracking.track_exception(exception) Gitlab::ErrorTracking.track_exception(exception)
end
end end
# This is used with GrapeLogging::Loggers::ExceptionLogger # This is used with GrapeLogging::Loggers::ExceptionLogger
......
...@@ -10,8 +10,6 @@ module API ...@@ -10,8 +10,6 @@ module API
api_endpoint = env['api.endpoint'] api_endpoint = env['api.endpoint']
feature_category = api_endpoint.options[:for].try(:feature_category_for_app, api_endpoint).to_s feature_category = api_endpoint.options[:for].try(:feature_category_for_app, api_endpoint).to_s
header[Gitlab::Metrics::RequestsRackMiddleware::FEATURE_CATEGORY_HEADER] = feature_category
Gitlab::ApplicationContext.push( Gitlab::ApplicationContext.push(
user: -> { actor&.user }, user: -> { actor&.user },
project: -> { project }, project: -> { project },
......
...@@ -44,6 +44,10 @@ module Gitlab ...@@ -44,6 +44,10 @@ module Gitlab
current.include?(Labkit::Context.log_key(attribute_name)) current.include?(Labkit::Context.log_key(attribute_name))
end end
def self.current_context_attribute(attribute_name)
Labkit::Context.current&.get_attribute(attribute_name)
end
def initialize(**args) def initialize(**args)
unknown_attributes = args.keys - APPLICATION_ATTRIBUTES.map(&:name) unknown_attributes = args.keys - APPLICATION_ATTRIBUTES.map(&:name)
raise ArgumentError, "#{unknown_attributes} are not known keys" if unknown_attributes.any? raise ArgumentError, "#{unknown_attributes} are not known keys" if unknown_attributes.any?
......
...@@ -67,10 +67,11 @@ module Gitlab ...@@ -67,10 +67,11 @@ module Gitlab
add_instrument_for_cache_hit(status_code, route, request) add_instrument_for_cache_hit(status_code, route, request)
Gitlab::ApplicationContext.push(feature_category: route.feature_category)
new_headers = { new_headers = {
'ETag' => etag, 'ETag' => etag,
'X-Gitlab-From-Cache' => 'true', 'X-Gitlab-From-Cache' => 'true'
::Gitlab::Metrics::RequestsRackMiddleware::FEATURE_CATEGORY_HEADER => route.feature_category
} }
[status_code, new_headers, []] [status_code, new_headers, []]
......
...@@ -15,7 +15,6 @@ module Gitlab ...@@ -15,7 +15,6 @@ module Gitlab
HEALTH_ENDPOINT = /^\/-\/(liveness|readiness|health|metrics)\/?$/.freeze HEALTH_ENDPOINT = /^\/-\/(liveness|readiness|health|metrics)\/?$/.freeze
FEATURE_CATEGORY_HEADER = 'X-Gitlab-Feature-Category'
FEATURE_CATEGORY_DEFAULT = 'unknown' FEATURE_CATEGORY_DEFAULT = 'unknown'
# These were the top 5 categories at a point in time, chosen as a # These were the top 5 categories at a point in time, chosen as a
...@@ -70,13 +69,11 @@ module Gitlab ...@@ -70,13 +69,11 @@ module Gitlab
started = Time.now.to_f started = Time.now.to_f
health_endpoint = health_endpoint?(env['PATH_INFO']) health_endpoint = health_endpoint?(env['PATH_INFO'])
status = 'undefined' status = 'undefined'
feature_category = nil
begin begin
status, headers, body = @app.call(env) status, headers, body = @app.call(env)
elapsed = Time.now.to_f - started elapsed = Time.now.to_f - started
feature_category = headers&.fetch(FEATURE_CATEGORY_HEADER, nil)
unless health_endpoint unless health_endpoint
RequestsRackMiddleware.http_request_duration_seconds.observe({ method: method }, elapsed) RequestsRackMiddleware.http_request_duration_seconds.observe({ method: method }, elapsed)
...@@ -104,6 +101,10 @@ module Gitlab ...@@ -104,6 +101,10 @@ module Gitlab
HEALTH_ENDPOINT.match?(CGI.unescape(path)) HEALTH_ENDPOINT.match?(CGI.unescape(path))
end end
def feature_category
::Gitlab::ApplicationContext.current_context_attribute(:feature_category)
end
end end
end end
end end
...@@ -68,6 +68,24 @@ RSpec.describe Gitlab::ApplicationContext do ...@@ -68,6 +68,24 @@ RSpec.describe Gitlab::ApplicationContext do
end end
end end
describe '.current_context_attribute' do
it 'returns the raw attribute value' do
described_class.with_context(caller_id: "Hello") do
expect(described_class.current_context_attribute(:caller_id)).to be('Hello')
end
end
it 'returns the attribute value with meta prefix' do
described_class.with_context(feature_category: "secure") do
expect(described_class.current_context_attribute('meta.feature_category')).to be('secure')
end
end
it 'returns nil if the key was not present in the current context' do
expect(described_class.current_context_attribute(:caller_id)).to be(nil)
end
end
describe '#to_lazy_hash' do describe '#to_lazy_hash' do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
......
...@@ -33,7 +33,6 @@ RSpec.describe Gitlab::EtagCaching::Middleware, :clean_gitlab_redis_shared_state ...@@ -33,7 +33,6 @@ RSpec.describe Gitlab::EtagCaching::Middleware, :clean_gitlab_redis_shared_state
expect(headers['ETag']).to be_nil expect(headers['ETag']).to be_nil
expect(headers['X-Gitlab-From-Cache']).to be_nil expect(headers['X-Gitlab-From-Cache']).to be_nil
expect(headers[::Gitlab::Metrics::RequestsRackMiddleware::FEATURE_CATEGORY_HEADER]).to be_nil
end end
it 'passes status code from app' do it 'passes status code from app' do
...@@ -41,6 +40,12 @@ RSpec.describe Gitlab::EtagCaching::Middleware, :clean_gitlab_redis_shared_state ...@@ -41,6 +40,12 @@ RSpec.describe Gitlab::EtagCaching::Middleware, :clean_gitlab_redis_shared_state
expect(status).to eq app_status_code expect(status).to eq app_status_code
end end
it 'does not set feature category attribute' do
expect(Gitlab::ApplicationContext).not_to receive(:push)
_, _, _ = middleware.call(build_request(path, if_none_match))
end
end end
context 'when there is no ETag in store for given resource' do context 'when there is no ETag in store for given resource' do
...@@ -164,8 +169,15 @@ RSpec.describe Gitlab::EtagCaching::Middleware, :clean_gitlab_redis_shared_state ...@@ -164,8 +169,15 @@ RSpec.describe Gitlab::EtagCaching::Middleware, :clean_gitlab_redis_shared_state
it 'sets correct headers' do it 'sets correct headers' do
_, headers, _ = middleware.call(build_request(path, if_none_match)) _, headers, _ = middleware.call(build_request(path, if_none_match))
expect(headers).to include('X-Gitlab-From-Cache' => 'true', expect(headers).to include('X-Gitlab-From-Cache' => 'true')
::Gitlab::Metrics::RequestsRackMiddleware::FEATURE_CATEGORY_HEADER => 'issue_tracking') end
it "pushes route's feature category to the context" do
expect(Gitlab::ApplicationContext).to receive(:push).with(
feature_category: 'issue_tracking'
)
_, _, _ = middleware.call(build_request(path, if_none_match))
end end
it_behaves_like 'sends a process_action.action_controller notification', 304 it_behaves_like 'sends a process_action.action_controller notification', 304
......
...@@ -7,8 +7,16 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do ...@@ -7,8 +7,16 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
subject { described_class.new(app) } subject { described_class.new(app) }
around do |example|
# Simulate application context middleware
# In fact, this middleware cleans up the contexts after a request lifecycle
::Gitlab::ApplicationContext.with_context({}) do
example.run
end
end
describe '#call' do describe '#call' do
let(:status) { 100 } let(:status) { 200 }
let(:env) { { 'REQUEST_METHOD' => 'GET' } } let(:env) { { 'REQUEST_METHOD' => 'GET' } }
let(:stack_result) { [status, {}, 'body'] } let(:stack_result) { [status, {}, 'body'] }
...@@ -91,9 +99,9 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do ...@@ -91,9 +99,9 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
end end
context 'feature category header' do context 'feature category header' do
context 'when a feature category header is present' do context 'when a feature category context is present' do
before do before do
allow(app).to receive(:call).and_return([200, { described_class::FEATURE_CATEGORY_HEADER => 'issue_tracking' }, nil]) ::Gitlab::ApplicationContext.push(feature_category: 'issue_tracking')
end end
it 'adds the feature category to the labels for http_requests_total' do it 'adds the feature category to the labels for http_requests_total' do
...@@ -113,11 +121,20 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do ...@@ -113,11 +121,20 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
end end
end end
context 'when the feature category header is an empty string' do context 'when application raises an exception when the feature category context is present' do
before do before do
allow(app).to receive(:call).and_return([200, { described_class::FEATURE_CATEGORY_HEADER => '' }, nil]) ::Gitlab::ApplicationContext.push(feature_category: 'issue_tracking')
allow(app).to receive(:call).and_raise(StandardError)
end
it 'adds the feature category to the labels for http_requests_total' do
expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: 'undefined', feature_category: 'issue_tracking')
expect { subject.call(env) }.to raise_error(StandardError)
end end
end
context 'when the feature category context is not available' do
it 'sets the feature category to unknown' do it 'sets the feature category to unknown' do
expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '200', feature_category: 'unknown') expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '200', feature_category: 'unknown')
expect(described_class).not_to receive(:http_health_requests_total) expect(described_class).not_to receive(:http_health_requests_total)
......
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