parent
7b0b241749
commit
170885edd6
7 changed files with 92 additions and 42 deletions
|
@ -4,6 +4,7 @@ v 8.12.0 (unreleased)
|
||||||
- Change merge_error column from string to text type
|
- Change merge_error column from string to text type
|
||||||
- Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
|
- Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
|
||||||
- Add `wiki_page_events` to project hook APIs (Ben Boeckel)
|
- Add `wiki_page_events` to project hook APIs (Ben Boeckel)
|
||||||
|
- Add Sentry logging to API calls
|
||||||
- Added tests for diff notes
|
- Added tests for diff notes
|
||||||
|
|
||||||
v 8.11.2 (unreleased)
|
v 8.11.2 (unreleased)
|
||||||
|
|
|
@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base
|
||||||
include Gitlab::GonHelper
|
include Gitlab::GonHelper
|
||||||
include GitlabRoutingHelper
|
include GitlabRoutingHelper
|
||||||
include PageLayoutHelper
|
include PageLayoutHelper
|
||||||
|
include SentryHelper
|
||||||
include WorkhorseHelper
|
include WorkhorseHelper
|
||||||
|
|
||||||
before_action :authenticate_user_from_private_token!
|
before_action :authenticate_user_from_private_token!
|
||||||
|
@ -46,28 +47,6 @@ class ApplicationController < ActionController::Base
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def sentry_context
|
|
||||||
if Rails.env.production? && current_application_settings.sentry_enabled
|
|
||||||
if current_user
|
|
||||||
Raven.user_context(
|
|
||||||
id: current_user.id,
|
|
||||||
email: current_user.email,
|
|
||||||
username: current_user.username,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
Raven.tags_context(program: sentry_program_context)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def sentry_program_context
|
|
||||||
if Sidekiq.server?
|
|
||||||
'sidekiq'
|
|
||||||
else
|
|
||||||
'rails'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# This filter handles both private tokens and personal access tokens
|
# This filter handles both private tokens and personal access tokens
|
||||||
def authenticate_user_from_private_token!
|
def authenticate_user_from_private_token!
|
||||||
token_string = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
|
token_string = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
|
||||||
|
|
27
app/helpers/sentry_helper.rb
Normal file
27
app/helpers/sentry_helper.rb
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
module SentryHelper
|
||||||
|
def sentry_enabled?
|
||||||
|
Rails.env.production? && current_application_settings.sentry_enabled?
|
||||||
|
end
|
||||||
|
|
||||||
|
def sentry_context
|
||||||
|
return unless sentry_enabled?
|
||||||
|
|
||||||
|
if current_user
|
||||||
|
Raven.user_context(
|
||||||
|
id: current_user.id,
|
||||||
|
email: current_user.email,
|
||||||
|
username: current_user.username,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
Raven.tags_context(program: sentry_program_context)
|
||||||
|
end
|
||||||
|
|
||||||
|
def sentry_program_context
|
||||||
|
if Sidekiq.server?
|
||||||
|
'sidekiq'
|
||||||
|
else
|
||||||
|
'rails'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -18,22 +18,14 @@ module API
|
||||||
end
|
end
|
||||||
|
|
||||||
rescue_from :all do |exception|
|
rescue_from :all do |exception|
|
||||||
# lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
|
handle_api_exception(exception)
|
||||||
# why is this not wrapped in something reusable?
|
|
||||||
trace = exception.backtrace
|
|
||||||
|
|
||||||
message = "\n#{exception.class} (#{exception.message}):\n"
|
|
||||||
message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
|
|
||||||
message << " " << trace.join("\n ")
|
|
||||||
|
|
||||||
API.logger.add Logger::FATAL, message
|
|
||||||
rack_response({ 'message' => '500 Internal Server Error' }.to_json, 500)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
format :json
|
format :json
|
||||||
content_type :txt, "text/plain"
|
content_type :txt, "text/plain"
|
||||||
|
|
||||||
# Ensure the namespace is right, otherwise we might load Grape::API::Helpers
|
# Ensure the namespace is right, otherwise we might load Grape::API::Helpers
|
||||||
|
helpers ::SentryHelper
|
||||||
helpers ::API::Helpers
|
helpers ::API::Helpers
|
||||||
|
|
||||||
mount ::API::AccessRequests
|
mount ::API::AccessRequests
|
||||||
|
|
|
@ -279,6 +279,24 @@ module API
|
||||||
error!({ 'message' => message }, status)
|
error!({ 'message' => message }, status)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_api_exception(exception)
|
||||||
|
if sentry_enabled? && report_exception?(exception)
|
||||||
|
define_params_for_grape_middleware
|
||||||
|
sentry_context
|
||||||
|
Raven.capture_exception(exception)
|
||||||
|
end
|
||||||
|
|
||||||
|
# lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
|
||||||
|
trace = exception.backtrace
|
||||||
|
|
||||||
|
message = "\n#{exception.class} (#{exception.message}):\n"
|
||||||
|
message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
|
||||||
|
message << " " << trace.join("\n ")
|
||||||
|
|
||||||
|
API.logger.add Logger::FATAL, message
|
||||||
|
rack_response({ 'message' => '500 Internal Server Error' }.to_json, 500)
|
||||||
|
end
|
||||||
|
|
||||||
# Projects helpers
|
# Projects helpers
|
||||||
|
|
||||||
def filter_projects(projects)
|
def filter_projects(projects)
|
||||||
|
@ -419,5 +437,19 @@ module API
|
||||||
Entities::Issue
|
Entities::Issue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The Grape Error Middleware only has access to env but no params. We workaround this by
|
||||||
|
# defining a method that returns the right value.
|
||||||
|
def define_params_for_grape_middleware
|
||||||
|
self.define_singleton_method(:params) { Rack::Request.new(env).params.symbolize_keys }
|
||||||
|
end
|
||||||
|
|
||||||
|
# We could get a Grape or a standard Ruby exception. We should only report anything that
|
||||||
|
# is clearly an error.
|
||||||
|
def report_exception?(exception)
|
||||||
|
return true unless exception.respond_to?(:status)
|
||||||
|
|
||||||
|
exception.status == 500
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,22 +9,14 @@ module Ci
|
||||||
end
|
end
|
||||||
|
|
||||||
rescue_from :all do |exception|
|
rescue_from :all do |exception|
|
||||||
# lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
|
handle_api_exception(exception)
|
||||||
# why is this not wrapped in something reusable?
|
|
||||||
trace = exception.backtrace
|
|
||||||
|
|
||||||
message = "\n#{exception.class} (#{exception.message}):\n"
|
|
||||||
message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
|
|
||||||
message << " " << trace.join("\n ")
|
|
||||||
|
|
||||||
API.logger.add Logger::FATAL, message
|
|
||||||
rack_response({ 'message' => '500 Internal Server Error' }, 500)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
content_type :txt, 'text/plain'
|
content_type :txt, 'text/plain'
|
||||||
content_type :json, 'application/json'
|
content_type :json, 'application/json'
|
||||||
format :json
|
format :json
|
||||||
|
|
||||||
|
helpers ::SentryHelper
|
||||||
helpers ::Ci::API::Helpers
|
helpers ::Ci::API::Helpers
|
||||||
helpers ::API::Helpers
|
helpers ::API::Helpers
|
||||||
helpers Gitlab::CurrentSettings
|
helpers Gitlab::CurrentSettings
|
||||||
|
|
|
@ -3,6 +3,7 @@ require 'spec_helper'
|
||||||
describe API::Helpers, api: true do
|
describe API::Helpers, api: true do
|
||||||
include API::Helpers
|
include API::Helpers
|
||||||
include ApiHelpers
|
include ApiHelpers
|
||||||
|
include SentryHelper
|
||||||
|
|
||||||
let(:user) { create(:user) }
|
let(:user) { create(:user) }
|
||||||
let(:admin) { create(:admin) }
|
let(:admin) { create(:admin) }
|
||||||
|
@ -234,4 +235,30 @@ describe API::Helpers, api: true do
|
||||||
expect(to_boolean(nil)).to be_nil
|
expect(to_boolean(nil)).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '.handle_api_exception' do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(self.class).to receive(:sentry_enabled?).and_return(true)
|
||||||
|
allow_any_instance_of(self.class).to receive(:rack_response)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not report a MethodNotAllowed exception to Sentry' do
|
||||||
|
exception = Grape::Exceptions::MethodNotAllowed.new({ 'X-GitLab-Test' => '1' })
|
||||||
|
allow(exception).to receive(:backtrace).and_return(caller)
|
||||||
|
|
||||||
|
expect(Raven).not_to receive(:capture_exception).with(exception)
|
||||||
|
|
||||||
|
handle_api_exception(exception)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does report RuntimeError to Sentry' do
|
||||||
|
exception = RuntimeError.new('test error')
|
||||||
|
allow(exception).to receive(:backtrace).and_return(caller)
|
||||||
|
|
||||||
|
expect_any_instance_of(self.class).to receive(:sentry_context)
|
||||||
|
expect(Raven).to receive(:capture_exception).with(exception)
|
||||||
|
|
||||||
|
handle_api_exception(exception)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue