airbrake/airbrake-ruby

View on GitHub
lib/airbrake-ruby/response.rb

Summary

Maintainability
A
1 hr
Test Coverage
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