summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Speicher <robert@gitlab.com>2016-08-24 22:42:02 +0000
committerRobert Speicher <robert@gitlab.com>2016-08-24 22:42:02 +0000
commit9ea01f32fe4355179da6082742b6ad06f9603388 (patch)
tree2d90474171f7909eb4601f610c95dd1a029c548a
parentf52cf56e90b2be3edb405fe588c94b637cf5088b (diff)
parent170885edd6f3ea52792511586778e0dce8021cf7 (diff)
downloadgitlab-ce-9ea01f32fe4355179da6082742b6ad06f9603388.tar.gz
Merge branch 'add-sentry-logging-to-api' into 'master'
Add Sentry logging to API calls ## What does this MR do? This MR adds support for Sentry logging in the API. ## Are there points in the code the reviewer needs to double check? Since the `Grape::Middleware` doesn't have a `params` method, I had to define one using the Rack Request. ## Why was this MR needed? We are missing a lot of useful errors in the API causing git push/pull errors ## What are the relevant issue numbers? #21043 See merge request !5882
-rw-r--r--CHANGELOG1
-rw-r--r--app/controllers/application_controller.rb23
-rw-r--r--app/helpers/sentry_helper.rb27
-rw-r--r--lib/api/api.rb12
-rw-r--r--lib/api/helpers.rb32
-rw-r--r--lib/ci/api/api.rb12
-rw-r--r--spec/requests/api/api_helpers_spec.rb27
7 files changed, 92 insertions, 42 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 8b25b94b772..3ccc292d826 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,6 +5,7 @@ v 8.12.0 (unreleased)
- Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel)
- Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
- Add `wiki_page_events` to project hook APIs (Ben Boeckel)
+ - Add Sentry logging to API calls
- Added tests for diff notes
- Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 634d36a4467..70a2275592b 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base
include Gitlab::GonHelper
include GitlabRoutingHelper
include PageLayoutHelper
+ include SentryHelper
include WorkhorseHelper
before_action :authenticate_user_from_private_token!
@@ -46,28 +47,6 @@ class ApplicationController < ActionController::Base
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
def authenticate_user_from_private_token!
token_string = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
diff --git a/app/helpers/sentry_helper.rb b/app/helpers/sentry_helper.rb
new file mode 100644
index 00000000000..f8cccade15b
--- /dev/null
+++ b/app/helpers/sentry_helper.rb
@@ -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
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 6b8bfbbdae6..ecbd5a6e2fa 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -18,22 +18,14 @@ module API
end
rescue_from :all do |exception|
- # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
- # 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)
+ handle_api_exception(exception)
end
format :json
content_type :txt, "text/plain"
# Ensure the namespace is right, otherwise we might load Grape::API::Helpers
+ helpers ::SentryHelper
helpers ::API::Helpers
mount ::API::AccessRequests
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index d0469d6602d..da4b1bf9902 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -279,6 +279,24 @@ module API
error!({ 'message' => message }, status)
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
def filter_projects(projects)
@@ -419,5 +437,19 @@ module API
Entities::Issue
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
diff --git a/lib/ci/api/api.rb b/lib/ci/api/api.rb
index 17bb99a2ae5..a6b9beecded 100644
--- a/lib/ci/api/api.rb
+++ b/lib/ci/api/api.rb
@@ -9,22 +9,14 @@ module Ci
end
rescue_from :all do |exception|
- # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
- # 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)
+ handle_api_exception(exception)
end
content_type :txt, 'text/plain'
content_type :json, 'application/json'
format :json
+ helpers ::SentryHelper
helpers ::Ci::API::Helpers
helpers ::API::Helpers
helpers Gitlab::CurrentSettings
diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb
index c65510fadec..bbdf8f03c2b 100644
--- a/spec/requests/api/api_helpers_spec.rb
+++ b/spec/requests/api/api_helpers_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe API::Helpers, api: true do
include API::Helpers
include ApiHelpers
+ include SentryHelper
let(:user) { create(:user) }
let(:admin) { create(:admin) }
@@ -234,4 +235,30 @@ describe API::Helpers, api: true do
expect(to_boolean(nil)).to be_nil
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