debian-mirror-gitlab/lib/sentry/client.rb

273 lines
7.6 KiB
Ruby
Raw Normal View History

2019-02-15 15:39:39 +05:30
# frozen_string_literal: true
module Sentry
class Client
2020-01-01 13:55:28 +05:30
include Sentry::Client::Projects
2019-02-15 15:39:39 +05:30
Error = Class.new(StandardError)
2019-07-07 11:18:12 +05:30
MissingKeysError = Class.new(StandardError)
2019-12-26 22:10:19 +05:30
ResponseInvalidSizeError = Class.new(StandardError)
2020-01-01 13:55:28 +05:30
BadRequestError = Class.new(StandardError)
SENTRY_API_SORT_VALUE_MAP = {
# <accepted_by_client> => <accepted_by_sentry_api>
'frequency' => 'freq',
'first_seen' => 'new',
'last_seen' => nil
}.freeze
2019-02-15 15:39:39 +05:30
attr_accessor :url, :token
def initialize(api_url, token)
@url = api_url
@token = token
end
2019-12-26 22:10:19 +05:30
def issue_details(issue_id:)
issue = get_issue(issue_id: issue_id)
map_to_detailed_error(issue)
end
def issue_latest_event(issue_id:)
latest_event = get_issue_latest_event(issue_id: issue_id)
map_to_event(latest_event)
end
2020-01-01 13:55:28 +05:30
def list_issues(**keyword_args)
response = get_issues(keyword_args)
2019-12-26 22:10:19 +05:30
2020-01-01 13:55:28 +05:30
issues = response[:issues]
pagination = response[:pagination]
2019-02-15 15:39:39 +05:30
2020-01-01 13:55:28 +05:30
validate_size(issues)
2019-07-07 11:18:12 +05:30
handle_mapping_exceptions do
2020-01-01 13:55:28 +05:30
{
issues: map_to_errors(issues),
pagination: pagination
}
2019-07-07 11:18:12 +05:30
end
2019-03-02 22:35:43 +05:30
end
2019-02-15 15:39:39 +05:30
private
2019-12-26 22:10:19 +05:30
def validate_size(issues)
return if Gitlab::Utils::DeepSize.new(issues).valid?
2020-01-01 13:55:28 +05:30
raise ResponseInvalidSizeError, "Sentry API response is too big. Limit is #{Gitlab::Utils::DeepSize.human_default_max_size}."
2019-12-26 22:10:19 +05:30
end
2019-07-07 11:18:12 +05:30
def handle_mapping_exceptions(&block)
yield
rescue KeyError => e
2020-01-01 13:55:28 +05:30
Gitlab::ErrorTracking.track_exception(e)
raise MissingKeysError, "Sentry API response is missing keys. #{e.message}"
2019-07-07 11:18:12 +05:30
end
2019-02-15 15:39:39 +05:30
def request_params
{
headers: {
'Authorization' => "Bearer #{@token}"
},
follow_redirects: false
}
end
2019-03-02 22:35:43 +05:30
def http_get(url, params = {})
2019-07-07 11:18:12 +05:30
response = handle_request_exceptions do
Gitlab::HTTP.get(url, **request_params.merge(params))
end
handle_response(response)
2019-02-15 15:39:39 +05:30
end
2020-01-01 13:55:28 +05:30
def get_issues(**keyword_args)
response = http_get(
issues_api_url,
query: list_issue_sentry_query(keyword_args)
)
{
issues: response[:body],
pagination: Sentry::PaginationParser.parse(response[:headers])
}
2019-03-02 22:35:43 +05:30
end
2020-01-01 13:55:28 +05:30
def list_issue_sentry_query(issue_status:, limit:, sort: nil, search_term: '', cursor: nil)
unless SENTRY_API_SORT_VALUE_MAP.key?(sort)
raise BadRequestError, 'Invalid value for sort param'
end
{
query: "is:#{issue_status} #{search_term}".strip,
limit: limit,
sort: SENTRY_API_SORT_VALUE_MAP[sort],
cursor: cursor
}.compact
2019-12-26 22:10:19 +05:30
end
2020-01-01 13:55:28 +05:30
def get_issue(issue_id:)
http_get(issue_api_url(issue_id))[:body]
2019-12-26 22:10:19 +05:30
end
2020-01-01 13:55:28 +05:30
def get_issue_latest_event(issue_id:)
http_get(issue_latest_event_api_url(issue_id))[:body]
2019-03-02 22:35:43 +05:30
end
2019-07-07 11:18:12 +05:30
def handle_request_exceptions
yield
2019-10-12 21:52:04 +05:30
rescue Gitlab::HTTP::Error => e
2020-01-01 13:55:28 +05:30
Gitlab::ErrorTracking.track_exception(e)
2019-07-07 11:18:12 +05:30
raise_error 'Error when connecting to Sentry'
rescue Net::OpenTimeout
raise_error 'Connection to Sentry timed out'
rescue SocketError
raise_error 'Received SocketError when trying to connect to Sentry'
rescue OpenSSL::SSL::SSLError
raise_error 'Sentry returned invalid SSL data'
rescue Errno::ECONNREFUSED
raise_error 'Connection refused'
rescue => e
2020-01-01 13:55:28 +05:30
Gitlab::ErrorTracking.track_exception(e)
2019-07-07 11:18:12 +05:30
raise_error "Sentry request failed due to #{e.class}"
end
2019-02-15 15:39:39 +05:30
def handle_response(response)
unless response.code == 200
2019-07-07 11:18:12 +05:30
raise_error "Sentry response status code: #{response.code}"
2019-02-15 15:39:39 +05:30
end
2020-01-01 13:55:28 +05:30
{ body: response.parsed_response, headers: response.headers }
2019-07-07 11:18:12 +05:30
end
def raise_error(message)
raise Client::Error, message
2019-02-15 15:39:39 +05:30
end
2019-12-26 22:10:19 +05:30
def issue_api_url(issue_id)
issue_url = URI(@url)
issue_url.path = "/api/0/issues/#{issue_id}/"
issue_url
end
def issue_latest_event_api_url(issue_id)
latest_event_url = URI(@url)
latest_event_url.path = "/api/0/issues/#{issue_id}/events/latest/"
latest_event_url
end
2019-02-15 15:39:39 +05:30
def issues_api_url
issues_url = URI(@url + '/issues/')
issues_url.path.squeeze!('/')
issues_url
end
def map_to_errors(issues)
2019-03-02 22:35:43 +05:30
issues.map(&method(:map_to_error))
end
2019-02-15 15:39:39 +05:30
def issue_url(id)
issues_url = @url + "/issues/#{id}"
2019-12-26 22:10:19 +05:30
parse_sentry_url(issues_url)
end
def project_url
parse_sentry_url(@url)
end
def parse_sentry_url(api_url)
url = ErrorTracking::ProjectErrorTrackingSetting.extract_sentry_external_url(api_url)
uri = URI(url)
2019-02-15 15:39:39 +05:30
uri.path.squeeze!('/')
2020-01-01 13:55:28 +05:30
# Remove trailing slash
2019-12-26 22:10:19 +05:30
uri = uri.to_s.gsub(/\/\z/, '')
2019-02-15 15:39:39 +05:30
2019-12-26 22:10:19 +05:30
uri
2019-02-15 15:39:39 +05:30
end
2019-12-26 22:10:19 +05:30
def map_to_event(event)
stack_trace = parse_stack_trace(event)
2019-02-15 15:39:39 +05:30
2019-12-26 22:10:19 +05:30
Gitlab::ErrorTracking::ErrorEvent.new(
issue_id: event.dig('groupID'),
date_received: event.dig('dateReceived'),
stack_trace_entries: stack_trace
)
end
2019-02-15 15:39:39 +05:30
2019-12-26 22:10:19 +05:30
def parse_stack_trace(event)
exception_entry = event.dig('entries')&.detect { |h| h['type'] == 'exception' }
return unless exception_entry
2019-02-15 15:39:39 +05:30
2019-12-26 22:10:19 +05:30
exception_values = exception_entry.dig('data', 'values')
stack_trace_entry = exception_values&.detect { |h| h['stacktrace'].present? }
return unless stack_trace_entry
stack_trace_entry.dig('stacktrace', 'frames')
end
2020-01-01 13:55:28 +05:30
def parse_gitlab_issue(plugin_issues)
return unless plugin_issues
gitlab_plugin = plugin_issues.detect { |item| item['id'] == 'gitlab' }
return unless gitlab_plugin
gitlab_plugin.dig('issue', 'url')
end
2019-12-26 22:10:19 +05:30
def map_to_detailed_error(issue)
Gitlab::ErrorTracking::DetailedError.new(
id: issue.fetch('id'),
first_seen: issue.fetch('firstSeen', nil),
last_seen: issue.fetch('lastSeen', nil),
title: issue.fetch('title', nil),
type: issue.fetch('type', nil),
user_count: issue.fetch('userCount', nil),
count: issue.fetch('count', nil),
message: issue.dig('metadata', 'value'),
culprit: issue.fetch('culprit', nil),
external_url: issue_url(issue.fetch('id')),
external_base_url: project_url,
short_id: issue.fetch('shortId', nil),
status: issue.fetch('status', nil),
frequency: issue.dig('stats', '24h'),
project_id: issue.dig('project', 'id'),
project_name: issue.dig('project', 'name'),
project_slug: issue.dig('project', 'slug'),
2020-01-01 13:55:28 +05:30
gitlab_issue: parse_gitlab_issue(issue.fetch('pluginIssues', nil)),
2019-12-26 22:10:19 +05:30
first_release_last_commit: issue.dig('firstRelease', 'lastCommit'),
last_release_last_commit: issue.dig('lastRelease', 'lastCommit'),
first_release_short_version: issue.dig('firstRelease', 'shortVersion'),
last_release_short_version: issue.dig('lastRelease', 'shortVersion')
)
end
2019-02-15 15:39:39 +05:30
2019-12-26 22:10:19 +05:30
def map_to_error(issue)
2019-02-15 15:39:39 +05:30
Gitlab::ErrorTracking::Error.new(
2019-12-26 22:10:19 +05:30
id: issue.fetch('id'),
2019-02-15 15:39:39 +05:30
first_seen: issue.fetch('firstSeen', nil),
last_seen: issue.fetch('lastSeen', nil),
title: issue.fetch('title', nil),
type: issue.fetch('type', nil),
user_count: issue.fetch('userCount', nil),
2019-12-26 22:10:19 +05:30
count: issue.fetch('count', nil),
message: issue.dig('metadata', 'value'),
2019-02-15 15:39:39 +05:30
culprit: issue.fetch('culprit', nil),
2019-12-26 22:10:19 +05:30
external_url: issue_url(issue.fetch('id')),
2019-02-15 15:39:39 +05:30
short_id: issue.fetch('shortId', nil),
status: issue.fetch('status', nil),
2019-12-26 22:10:19 +05:30
frequency: issue.dig('stats', '24h'),
2019-07-07 11:18:12 +05:30
project_id: issue.dig('project', 'id'),
project_name: issue.dig('project', 'name'),
project_slug: issue.dig('project', 'slug')
2019-02-15 15:39:39 +05:30
)
end
end
end