diff options
Diffstat (limited to 'lib/api/error_tracking/collector.rb')
-rw-r--r-- | lib/api/error_tracking/collector.rb | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/lib/api/error_tracking/collector.rb b/lib/api/error_tracking/collector.rb new file mode 100644 index 00000000000..22fbd3a1118 --- /dev/null +++ b/lib/api/error_tracking/collector.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +module API + # This API is responsible for collecting error tracking information + # from sentry client. It allows us to use GitLab as an alternative to + # sentry backend. For more details see https://gitlab.com/gitlab-org/gitlab/-/issues/329596. + class ErrorTracking::Collector < ::API::Base + feature_category :error_tracking + + content_type :envelope, 'application/x-sentry-envelope' + content_type :json, 'application/json' + content_type :txt, 'text/plain' + default_format :envelope + + before do + not_found!('Project') unless project + not_found! unless feature_enabled? + not_found! unless active_client_key? + end + + helpers do + def project + @project ||= find_project(params[:id]) + end + + def feature_enabled? + project.error_tracking_setting&.enabled? && + project.error_tracking_setting&.integrated_client? + end + + def find_client_key(public_key) + return unless public_key.present? + + project.error_tracking_client_keys.active.find_by_public_key(public_key) + end + + def active_client_key? + public_key = extract_public_key + + find_client_key(public_key) + end + + def extract_public_key + # Some SDK send public_key as a param. In this case we don't need to parse headers. + return params[:sentry_key] if params[:sentry_key].present? + + begin + ::ErrorTracking::Collector::SentryAuthParser.parse(request)[:public_key] + rescue StandardError + bad_request!('Failed to parse sentry request') + end + end + end + + desc 'Submit error tracking event to the project as envelope' do + detail 'This feature was introduced in GitLab 14.1.' + end + params do + requires :id, type: String, desc: 'The ID of a project' + end + post 'error_tracking/collector/api/:id/envelope' do + # There is a reason why we have such uncommon path. + # We depend on a client side error tracking software which + # modifies URL for its own reasons. + # + # When we give user a URL like this + # HOST/api/v4/error_tracking/collector/123 + # + # Then error tracking software will convert it like this: + # HOST/api/v4/error_tracking/collector/api/123/envelope/ + + begin + parsed_request = ::ErrorTracking::Collector::SentryRequestParser.parse(request) + rescue StandardError + bad_request!('Failed to parse sentry request') + end + + type = parsed_request[:request_type] + + # Sentry sends 2 requests on each exception: transaction and event. + # Everything else is not a desired behavior. + unless type == 'transaction' || type == 'event' + render_api_error!('400 Bad Request', 400) + + break + end + + # We don't have use for transaction request yet, + # so we record only event one. + if type == 'event' + ::ErrorTracking::CollectErrorService + .new(project, nil, event: parsed_request[:event]) + .execute + end + + # Collector should never return any information back. + # Because DSN and public key are designed for public use, + # it is safe only for submission of new events. + no_content! + end + + desc 'Submit error tracking event to the project' do + detail 'This feature was introduced in GitLab 14.1.' + end + params do + requires :id, type: String, desc: 'The ID of a project' + end + post 'error_tracking/collector/api/:id/store' do + # There is a reason why we have such uncommon path. + # We depend on a client side error tracking software which + # modifies URL for its own reasons. + # + # When we give user a URL like this + # HOST/api/v4/error_tracking/collector/123 + # + # Then error tracking software will convert it like this: + # HOST/api/v4/error_tracking/collector/api/123/store/ + + begin + parsed_body = Gitlab::Json.parse(request.body.read) + rescue StandardError + bad_request!('Failed to parse sentry request') + end + + ::ErrorTracking::CollectErrorService + .new(project, nil, event: parsed_body) + .execute + + # Collector should never return any information back. + # Because DSN and public key are designed for public use, + # it is safe only for submission of new events. + no_content! + end + end +end |