lib/airbrake-ruby/response.rb
module Airbrake # Parses responses coming from the Airbrake API. Handles HTTP errors by # logging them. # # @api private # @since v1.0.0 module Response # @return [Integer] the limit of the response body TRUNCATE_LIMIT = 100 # @return [Integer] HTTP code returned when the server cannot or will not # process the request due to something that is perceived to be a client # error # @since v6.2.0 BAD_REQUEST = 400 # @return [Integer] HTTP code returned when client request has not been # completed because it lacks valid authentication credentials for the # requested resource # @since v6.2.0 UNAUTHORIZED = 401 # @return [Integer] HTTP code returned when the server understands the # request but refuses to authorize it # @since v6.2.0 FORBIDDEN = 403 # @return [Integer] HTTP code returned when the server would like to shut # down this unused connection # @since v6.2.0 REQUEST_TIMEOUT = 408 # @return [Integer] HTTP code returned when there's a request conflict with # the current state of the target resource # @since v6.2.0 CONFLICT = 409 # @return [Integer] # @since v6.2.0 ENHANCE_YOUR_CALM = 420 # @return [Integer] HTTP code returned when an IP sends over 10k/min notices TOO_MANY_REQUESTS = 429 # @return [Integer] HTTP code returned when the server encountered an # unexpected condition that prevented it from fulfilling the request # @since v6.2.0 INTERNAL_SERVER_ERROR = 500 # @return [Integer] HTTP code returened when the server, while acting as a # gateway or proxy, received an invalid response from the upstream server # @since v6.2.0 BAD_GATEWAY = 502 # @return [Integer] HTTP code returened when the server, while acting as a # gateway or proxy, did not get a response in time from the upstream # server that it needed in order to complete the request # @since v6.2.0 GATEWAY_TIMEOUT = 504 class << self include Loggable end # Parses HTTP responses from the Airbrake API. # # @param [Net::HTTPResponse] response # @return [Hash{String=>String}] parsed response # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def self.parse(response) code = response.code.to_i body = response.body begin case code when 200, 204 logger.debug("#{LOG_LABEL} #{name} (#{code}): #{body}") { response.msg => response.body } when 201 parsed_body = JSON.parse(body) logger.debug("#{LOG_LABEL} #{name} (#{code}): #{parsed_body}") parsed_body when BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, ENHANCE_YOUR_CALM parsed_body = JSON.parse(body) logger.error("#{LOG_LABEL} #{parsed_body['message']}") parsed_body.merge('code' => code, 'error' => parsed_body['message']) when TOO_MANY_REQUESTS parsed_body = JSON.parse(body) msg = "#{LOG_LABEL} #{parsed_body['message']}" logger.error(msg) { 'code' => code, 'error' => msg, 'rate_limit_reset' => rate_limit_reset(response), } else body_msg = truncated_body(body) logger.error("#{LOG_LABEL} unexpected code (#{code}). Body: #{body_msg}") { 'code' => code, 'error' => body_msg } end rescue StandardError => ex body_msg = truncated_body(body) logger.error("#{LOG_LABEL} error while parsing body (#{ex}). Body: #{body_msg}") { 'code' => code, 'error' => ex.inspect } end end # rubocop:enable Metrics/MethodLength, Metrics/AbcSize def self.truncated_body(body) if body.nil? '[EMPTY_BODY]'.freeze elsif body.length > TRUNCATE_LIMIT body[0..TRUNCATE_LIMIT] << '...' else body end end private_class_method :truncated_body def self.rate_limit_reset(response) Time.now + response['X-RateLimit-Delay'].to_i end private_class_method :rate_limit_reset end end