Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gitlab/error_tracking/error_repository/open_api_strategy.rb')
-rw-r--r--lib/gitlab/error_tracking/error_repository/open_api_strategy.rb248
1 files changed, 248 insertions, 0 deletions
diff --git a/lib/gitlab/error_tracking/error_repository/open_api_strategy.rb b/lib/gitlab/error_tracking/error_repository/open_api_strategy.rb
new file mode 100644
index 00000000000..e3eae20c520
--- /dev/null
+++ b/lib/gitlab/error_tracking/error_repository/open_api_strategy.rb
@@ -0,0 +1,248 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ErrorTracking
+ class ErrorRepository
+ class OpenApiStrategy
+ def initialize(project)
+ @project = project
+
+ api_url = configured_api_url
+
+ open_api.configure do |config|
+ config.scheme = api_url.scheme
+ config.host = [api_url.host, api_url.port].compact.join(':')
+ config.server_index = nil
+ config.logger = Gitlab::AppLogger
+ end
+ end
+
+ def report_error(
+ name:, description:, actor:, platform:,
+ environment:, level:, occurred_at:, payload:
+ )
+ raise NotImplementedError, 'Use ingestion endpoint'
+ end
+
+ def find_error(id)
+ api = open_api::ErrorsApi.new
+ error = api.get_error(project_id, id)
+
+ to_sentry_detailed_error(error)
+ rescue ErrorTrackingOpenAPI::ApiError => e
+ log_exception(e)
+ nil
+ end
+
+ def list_errors(filters:, query:, sort:, limit:, cursor:)
+ opts = {
+ sort: "#{sort}_desc",
+ status: filters[:status],
+ query: query,
+ cursor: cursor,
+ limit: limit
+ }.compact
+
+ api = open_api::ErrorsApi.new
+ errors, _status, headers = api.list_errors_with_http_info(project_id, opts)
+ pagination = pagination_from_headers(headers)
+
+ if errors.size < limit
+ # Don't show next link if amount of errors is less then requested.
+ # This a workaround until the Golang backend returns link cursor
+ # only if there is a next page.
+ pagination.next = nil
+ end
+
+ [errors.map { to_sentry_error(_1) }, pagination]
+ rescue ErrorTrackingOpenAPI::ApiError => e
+ log_exception(e)
+ [[], ErrorRepository::Pagination.new]
+ end
+
+ def last_event_for(id)
+ event = newest_event_for(id)
+ return unless event
+
+ api = open_api::ErrorsApi.new
+ error = api.get_error(project_id, id)
+ return unless error
+
+ to_sentry_error_event(event, error)
+ rescue ErrorTrackingOpenAPI::ApiError => e
+ log_exception(e)
+ nil
+ end
+
+ def update_error(id, **attributes)
+ opts = attributes.slice(:status)
+
+ body = open_api::ErrorUpdatePayload.new(opts)
+
+ api = open_api::ErrorsApi.new
+ api.update_error(project_id, id, body)
+
+ true
+ rescue ErrorTrackingOpenAPI::ApiError => e
+ log_exception(e)
+ false
+ end
+
+ def dsn_url(public_key)
+ config = open_api::Configuration.default
+
+ base_url = [
+ config.scheme,
+ "://",
+ public_key,
+ '@',
+ config.host,
+ config.base_path
+ ].join('')
+
+ "#{base_url}/projects/api/#{project_id}"
+ end
+
+ private
+
+ def event_for(id, sort:)
+ opts = { sort: sort, limit: 1 }
+
+ api = open_api::ErrorsApi.new
+ api.list_events(project_id, id, opts).first
+ rescue ErrorTrackingOpenAPI::ApiError => e
+ log_exception(e)
+ nil
+ end
+
+ def newest_event_for(id)
+ event_for(id, sort: 'occurred_at_desc')
+ end
+
+ def oldest_event_for(id)
+ event_for(id, sort: 'occurred_at_asc')
+ end
+
+ def to_sentry_error(error)
+ Gitlab::ErrorTracking::Error.new(
+ id: error.fingerprint.to_s,
+ title: error.name,
+ message: error.description,
+ culprit: error.actor,
+ first_seen: error.first_seen_at,
+ last_seen: error.last_seen_at,
+ status: error.status,
+ count: error.event_count,
+ user_count: error.approximated_user_count
+ )
+ end
+
+ def to_sentry_detailed_error(error)
+ Gitlab::ErrorTracking::DetailedError.new(
+ id: error.fingerprint.to_s,
+ title: error.name,
+ message: error.description,
+ culprit: error.actor,
+ first_seen: error.first_seen_at.to_s,
+ last_seen: error.last_seen_at.to_s,
+ count: error.event_count,
+ user_count: error.approximated_user_count,
+ project_id: error.project_id,
+ status: error.status,
+ tags: { level: nil, logger: nil },
+ external_url: external_url(error.fingerprint),
+ external_base_url: external_base_url,
+ integrated: true,
+ first_release_version: release_from(oldest_event_for(error.fingerprint)),
+ last_release_version: release_from(newest_event_for(error.fingerprint))
+ )
+ end
+
+ def to_sentry_error_event(event, error)
+ Gitlab::ErrorTracking::ErrorEvent.new(
+ issue_id: event.fingerprint.to_s,
+ date_received: error.last_seen_at,
+ stack_trace_entries: build_stacktrace(event)
+ )
+ end
+
+ def pagination_from_headers(headers)
+ links = headers['link'].to_s.split(', ')
+
+ pagination_hash = links.map { parse_pagination_link(_1) }.compact.to_h
+
+ ErrorRepository::Pagination.new(pagination_hash['next'], pagination_hash['prev'])
+ end
+
+ LINK_PATTERN = %r{cursor=(?<cursor>[^&]+).*; rel="(?<direction>\w+)"}.freeze
+
+ def parse_pagination_link(content)
+ match = LINK_PATTERN.match(content)
+ return unless match
+
+ [match['direction'], CGI.unescape(match['cursor'])]
+ end
+
+ def build_stacktrace(event)
+ payload = parse_json(event.payload)
+ return [] unless payload
+
+ ::ErrorTracking::StacktraceBuilder.new(payload).stacktrace
+ end
+
+ def parse_json(payload)
+ Gitlab::Json.parse(payload)
+ rescue JSON::ParserError
+ end
+
+ def release_from(event)
+ return unless event
+
+ payload = parse_json(event.payload)
+ return unless payload
+
+ payload['release']
+ end
+
+ def project_id
+ @project.id
+ end
+
+ def open_api
+ ErrorTrackingOpenAPI
+ end
+
+ # For compatibility with sentry integration
+ def external_url(id)
+ Gitlab::Routing.url_helpers.details_namespace_project_error_tracking_index_url(
+ namespace_id: @project.namespace,
+ project_id: @project,
+ issue_id: id)
+ end
+
+ # For compatibility with sentry integration
+ def external_base_url
+ Gitlab::Routing.url_helpers.project_url(@project)
+ end
+
+ def configured_api_url
+ url = Gitlab::CurrentSettings.current_application_settings.error_tracking_api_url ||
+ 'http://localhost:8080'
+
+ Gitlab::UrlBlocker.validate!(url, schemes: %w[http https], allow_localhost: true)
+
+ URI(url)
+ end
+
+ def log_exception(exception)
+ params = {
+ http_code: exception.code,
+ response_body: exception.response_body&.truncate(100)
+ }
+
+ Gitlab::AppLogger.error(Gitlab::Utils::InlineHash.merge_keys(params, prefix: 'open_api'))
+ end
+ end
+ end
+ end
+end