api_hammer 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/CHANGELOG.md +5 -0
 - data/lib/api_hammer/faraday/request_logger.rb +80 -33
 - data/lib/api_hammer/request_logger.rb +46 -28
 - data/lib/api_hammer/version.rb +1 -1
 - data/test/faraday_request_logger_test.rb +20 -0
 - data/test/request_logger_test.rb +21 -0
 - metadata +2 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA1:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: b342d8c401e21b4f1de5a9bca122654c97029575
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 43ebdf3104bb5f99a077b1f5a6865175be99ae0b
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 354e49d66f033993d760bb2e465e8166330ec28be9f2bc7cf2d1c385fb887b3c5a06e744f60be19cbb3ed52c84024c41d6603004860628b50ecc37b6f3b94b88
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 38e03dedd7c08c876b1f91cb7fd3b68da53b3dc045e71787d753a653d0952c10d884a6f36d7ca993e9de533b2fc19cd5462daba5131c4847250b2fb2324cfb6c
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    
| 
         @@ -12,6 +12,45 @@ if Faraday::Request.respond_to?(:register_middleware) 
     | 
|
| 
       12 
12 
     | 
    
         
             
            end
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
14 
     | 
    
         
             
            module ApiHammer
         
     | 
| 
      
 15 
     | 
    
         
            +
              # parses attributes out of content type header
         
     | 
| 
      
 16 
     | 
    
         
            +
              class ContentTypeAttrs
         
     | 
| 
      
 17 
     | 
    
         
            +
                def initialize(content_type)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @media_type = content_type.split(/\s*[;]\s*/, 2).first if content_type
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @media_type.strip! if @media_type
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @content_type = content_type
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @parsed = false
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @attributes = Hash.new { |h,k| h[k] = [] }
         
     | 
| 
      
 23 
     | 
    
         
            +
                  catch(:unparseable) do
         
     | 
| 
      
 24 
     | 
    
         
            +
                    throw(:unparseable) unless content_type
         
     | 
| 
      
 25 
     | 
    
         
            +
                    uri_parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI
         
     | 
| 
      
 26 
     | 
    
         
            +
                    scanner = StringScanner.new(content_type)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    scanner.scan(/.*;\s*/) || throw(:unparseable)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    while match = scanner.scan(/(\w+)=("?)([^"]*)("?)\s*(,?)\s*/)
         
     | 
| 
      
 29 
     | 
    
         
            +
                      key = scanner[1]
         
     | 
| 
      
 30 
     | 
    
         
            +
                      quote1 = scanner[2]
         
     | 
| 
      
 31 
     | 
    
         
            +
                      value = scanner[3]
         
     | 
| 
      
 32 
     | 
    
         
            +
                      quote2 = scanner[4]
         
     | 
| 
      
 33 
     | 
    
         
            +
                      comma_follows = !scanner[5].empty?
         
     | 
| 
      
 34 
     | 
    
         
            +
                      throw(:unparseable) unless quote1 == quote2
         
     | 
| 
      
 35 
     | 
    
         
            +
                      throw(:unparseable) if !comma_follows && !scanner.eos?
         
     | 
| 
      
 36 
     | 
    
         
            +
                      @attributes[uri_parser.unescape(key)] << uri_parser.unescape(value)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    end
         
     | 
| 
      
 38 
     | 
    
         
            +
                    throw(:unparseable) unless scanner.eos?
         
     | 
| 
      
 39 
     | 
    
         
            +
                    @parsed = true
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                attr_reader :media_type
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def parsed?
         
     | 
| 
      
 46 
     | 
    
         
            +
                  @parsed
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                def [](key)
         
     | 
| 
      
 50 
     | 
    
         
            +
                  @attributes[key]
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
              end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
       15 
54 
     | 
    
         
             
              class Faraday
         
     | 
| 
       16 
55 
     | 
    
         
             
                # Faraday middleware for logging.
         
     | 
| 
       17 
56 
     | 
    
         
             
                #
         
     | 
| 
         @@ -40,36 +79,15 @@ module ApiHammer 
     | 
|
| 
       40 
79 
     | 
    
         
             
                      response_body.force_encoding('ASCII-8BIT')
         
     | 
| 
       41 
80 
     | 
    
         
             
                    end
         
     | 
| 
       42 
81 
     | 
    
         | 
| 
       43 
     | 
    
         
            -
                     
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
                       
     | 
| 
       46 
     | 
    
         
            -
                       
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
                         
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
                          key = scanner[1]
         
     | 
| 
       53 
     | 
    
         
            -
                          quote1 = scanner[2]
         
     | 
| 
       54 
     | 
    
         
            -
                          value = scanner[3]
         
     | 
| 
       55 
     | 
    
         
            -
                          quote2 = scanner[4]
         
     | 
| 
       56 
     | 
    
         
            -
                          comma_follows = !scanner[5].empty?
         
     | 
| 
       57 
     | 
    
         
            -
                          throw(:unparseable) unless quote1 == quote2
         
     | 
| 
       58 
     | 
    
         
            -
                          throw(:unparseable) if !comma_follows && !scanner.eos?
         
     | 
| 
       59 
     | 
    
         
            -
                          attributes[uri_parser.unescape(key)] << uri_parser.unescape(value)
         
     | 
| 
       60 
     | 
    
         
            -
                        end
         
     | 
| 
       61 
     | 
    
         
            -
                        throw(:unparseable) unless scanner.eos?
         
     | 
| 
       62 
     | 
    
         
            -
                        parsed = true
         
     | 
| 
       63 
     | 
    
         
            -
                      end
         
     | 
| 
       64 
     | 
    
         
            -
                      if parsed
         
     | 
| 
       65 
     | 
    
         
            -
                        charset = attributes['charset'].first
         
     | 
| 
       66 
     | 
    
         
            -
                        if charset && Encoding.list.any? { |enc| enc.to_s.downcase == charset.downcase }
         
     | 
| 
       67 
     | 
    
         
            -
                          if response_body.dup.force_encoding(charset).valid_encoding?
         
     | 
| 
       68 
     | 
    
         
            -
                            response_body.force_encoding(charset)
         
     | 
| 
       69 
     | 
    
         
            -
                          else
         
     | 
| 
       70 
     | 
    
         
            -
                            # I guess just ignore the specified encoding if the result is not valid. fall back to 
         
     | 
| 
       71 
     | 
    
         
            -
                            # something else below.
         
     | 
| 
       72 
     | 
    
         
            -
                          end
         
     | 
| 
      
 82 
     | 
    
         
            +
                    content_type_attrs = ContentTypeAttrs.new(content_type)
         
     | 
| 
      
 83 
     | 
    
         
            +
                    if content_type_attrs.parsed?
         
     | 
| 
      
 84 
     | 
    
         
            +
                      charset = content_type_attrs['charset'].first
         
     | 
| 
      
 85 
     | 
    
         
            +
                      if charset && Encoding.list.any? { |enc| enc.to_s.downcase == charset.downcase }
         
     | 
| 
      
 86 
     | 
    
         
            +
                        if response_body.dup.force_encoding(charset).valid_encoding?
         
     | 
| 
      
 87 
     | 
    
         
            +
                          response_body.force_encoding(charset)
         
     | 
| 
      
 88 
     | 
    
         
            +
                        else
         
     | 
| 
      
 89 
     | 
    
         
            +
                          # I guess just ignore the specified encoding if the result is not valid. fall back to 
         
     | 
| 
      
 90 
     | 
    
         
            +
                          # something else below.
         
     | 
| 
       73 
91 
     | 
    
         
             
                        end
         
     | 
| 
       74 
92 
     | 
    
         
             
                      end
         
     | 
| 
       75 
93 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -79,7 +97,7 @@ module ApiHammer 
     | 
|
| 
       79 
97 
     | 
    
         
             
                      # if updating by content-type didn't do it, try UTF8 since JSON wants that - but only 
         
     | 
| 
       80 
98 
     | 
    
         
             
                      # if it seems to be valid utf8. 
         
     | 
| 
       81 
99 
     | 
    
         
             
                      # don't try utf8 if the response content-type indicated something else. 
         
     | 
| 
       82 
     | 
    
         
            -
                      try_utf8 = !(parsed &&  
     | 
| 
      
 100 
     | 
    
         
            +
                      try_utf8 = !(content_type_attrs && content_type_attrs.parsed? && content_type_attrs['charset'].any?)
         
     | 
| 
       83 
101 
     | 
    
         
             
                      if try_utf8 && response_body.dup.force_encoding('UTF-8').valid_encoding?
         
     | 
| 
       84 
102 
     | 
    
         
             
                        response_body.force_encoding('UTF-8')
         
     | 
| 
       85 
103 
     | 
    
         
             
                      else
         
     | 
| 
         @@ -92,6 +110,35 @@ module ApiHammer 
     | 
|
| 
       92 
110 
     | 
    
         
             
                    response_body
         
     | 
| 
       93 
111 
     | 
    
         
             
                  end
         
     | 
| 
       94 
112 
     | 
    
         | 
| 
      
 113 
     | 
    
         
            +
                  def text?(content_type)
         
     | 
| 
      
 114 
     | 
    
         
            +
                    content_type_attrs = ContentTypeAttrs.new(content_type)
         
     | 
| 
      
 115 
     | 
    
         
            +
                    media_type = content_type_attrs.media_type
         
     | 
| 
      
 116 
     | 
    
         
            +
                    # ordered hash by priority mapping types to binary or text
         
     | 
| 
      
 117 
     | 
    
         
            +
                    # regexps will have \A and \z added 
         
     | 
| 
      
 118 
     | 
    
         
            +
                    types = {
         
     | 
| 
      
 119 
     | 
    
         
            +
                      %r(image/.*) => :binary,
         
     | 
| 
      
 120 
     | 
    
         
            +
                      %r(audio/.*) => :binary,
         
     | 
| 
      
 121 
     | 
    
         
            +
                      %r(video/.*) => :binary,
         
     | 
| 
      
 122 
     | 
    
         
            +
                      %r(model/.*) => :binary,
         
     | 
| 
      
 123 
     | 
    
         
            +
                      %r(text/.*) => :text,
         
     | 
| 
      
 124 
     | 
    
         
            +
                      %r(message/.*) => :text,
         
     | 
| 
      
 125 
     | 
    
         
            +
                      'application/octet-stream' => :binary,
         
     | 
| 
      
 126 
     | 
    
         
            +
                      'application/ogg' => :binary,
         
     | 
| 
      
 127 
     | 
    
         
            +
                      'application/pdf' => :binary,
         
     | 
| 
      
 128 
     | 
    
         
            +
                      'application/postscript' => :binary,
         
     | 
| 
      
 129 
     | 
    
         
            +
                      'application/zip' => :binary,
         
     | 
| 
      
 130 
     | 
    
         
            +
                      'application/gzip' => :binary,
         
     | 
| 
      
 131 
     | 
    
         
            +
                    }
         
     | 
| 
      
 132 
     | 
    
         
            +
                    types.each do |match, type|
         
     | 
| 
      
 133 
     | 
    
         
            +
                      matched = match.is_a?(Regexp) ? media_type =~ %r(\A#{match.source}\z) : media_type == match
         
     | 
| 
      
 134 
     | 
    
         
            +
                      if matched
         
     | 
| 
      
 135 
     | 
    
         
            +
                        return type == :text
         
     | 
| 
      
 136 
     | 
    
         
            +
                      end
         
     | 
| 
      
 137 
     | 
    
         
            +
                    end
         
     | 
| 
      
 138 
     | 
    
         
            +
                    # fallback (unknown or not given) assume text
         
     | 
| 
      
 139 
     | 
    
         
            +
                    return true
         
     | 
| 
      
 140 
     | 
    
         
            +
                  end
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
       95 
142 
     | 
    
         
             
                  def call(request_env)
         
     | 
| 
       96 
143 
     | 
    
         
             
                    began_at = Time.now
         
     | 
| 
       97 
144 
     | 
    
         | 
| 
         @@ -119,12 +166,12 @@ module ApiHammer 
     | 
|
| 
       119 
166 
     | 
    
         
             
                          'method' => request_env[:method],
         
     | 
| 
       120 
167 
     | 
    
         
             
                          'uri' => request_env[:url].normalize.to_s,
         
     | 
| 
       121 
168 
     | 
    
         
             
                          'headers' => request_env.request_headers,
         
     | 
| 
       122 
     | 
    
         
            -
                          'body' => request_body,
         
     | 
| 
      
 169 
     | 
    
         
            +
                          'body' => (request_body if text?(request_env.request_headers['Content-Type'])),
         
     | 
| 
       123 
170 
     | 
    
         
             
                        }.reject{|k,v| v.nil? },
         
     | 
| 
       124 
171 
     | 
    
         
             
                        'response' => {
         
     | 
| 
       125 
172 
     | 
    
         
             
                          'status' => response_env.status,
         
     | 
| 
       126 
173 
     | 
    
         
             
                          'headers' => response_env.response_headers,
         
     | 
| 
       127 
     | 
    
         
            -
                          'body' => response_body(response_env),
         
     | 
| 
      
 174 
     | 
    
         
            +
                          'body' => (response_body(response_env) if text?(response_env.response_headers['Content-Type'])),
         
     | 
| 
       128 
175 
     | 
    
         
             
                        }.reject{|k,v| v.nil? },
         
     | 
| 
       129 
176 
     | 
    
         
             
                        'processing' => {
         
     | 
| 
       130 
177 
     | 
    
         
             
                          'began_at' => began_at.utc.to_i,
         
     | 
| 
         @@ -20,23 +20,25 @@ module ApiHammer 
     | 
|
| 
       20 
20 
     | 
    
         | 
| 
       21 
21 
     | 
    
         
             
                  # this is closed after the app is called, so read it before 
         
     | 
| 
       22 
22 
     | 
    
         
             
                  env["rack.input"].rewind
         
     | 
| 
       23 
     | 
    
         
            -
                   
     | 
| 
      
 23 
     | 
    
         
            +
                  request_body = env["rack.input"].read
         
     | 
| 
       24 
24 
     | 
    
         
             
                  env["rack.input"].rewind
         
     | 
| 
       25 
25 
     | 
    
         | 
| 
       26 
26 
     | 
    
         
             
                  log_tags = Thread.current[:activesupport_tagged_logging_tags]
         
     | 
| 
       27 
     | 
    
         
            -
                   
     | 
| 
      
 27 
     | 
    
         
            +
                  log_tags = log_tags.dup if log_tags && log_tags.any?
         
     | 
| 
       28 
28 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
                  status,  
     | 
| 
       30 
     | 
    
         
            -
                   
     | 
| 
       31 
     | 
    
         
            -
                  body_proxy = ::Rack::BodyProxy.new( 
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
      
 29 
     | 
    
         
            +
                  status, response_headers, response_body = @app.call(env)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  response_headers = ::Rack::Utils::HeaderHash.new(response_headers)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  body_proxy = ::Rack::BodyProxy.new(response_body) do
         
     | 
| 
      
 32 
     | 
    
         
            +
                    log(env, request_body, status, response_headers, response_body, began_at, log_tags)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
                  [status, response_headers, body_proxy]
         
     | 
| 
       33 
35 
     | 
    
         
             
                end
         
     | 
| 
       34 
36 
     | 
    
         | 
| 
       35 
     | 
    
         
            -
                def log(env, status,  
     | 
| 
      
 37 
     | 
    
         
            +
                def log(env, request_body, status, response_headers, response_body, began_at, log_tags)
         
     | 
| 
       36 
38 
     | 
    
         
             
                  now = Time.now
         
     | 
| 
       37 
39 
     | 
    
         | 
| 
       38 
40 
     | 
    
         
             
                  request = Rack::Request.new(env)
         
     | 
| 
       39 
     | 
    
         
            -
                  response = Rack::Response.new('', status,  
     | 
| 
      
 41 
     | 
    
         
            +
                  response = Rack::Response.new('', status, response_headers)
         
     | 
| 
       40 
42 
     | 
    
         | 
| 
       41 
43 
     | 
    
         
             
                  request_uri = Addressable::URI.new(
         
     | 
| 
       42 
44 
     | 
    
         
             
                    :scheme => request.scheme,
         
     | 
| 
         @@ -56,51 +58,67 @@ module ApiHammer 
     | 
|
| 
       56 
58 
     | 
    
         
             
                    :white
         
     | 
| 
       57 
59 
     | 
    
         
             
                  end
         
     | 
| 
       58 
60 
     | 
    
         
             
                  status_s = bold(send(status_color, status.to_s))
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                  request_headers = env.map do |(key, value)|
         
     | 
| 
      
 63 
     | 
    
         
            +
                    http_match = key.match(/\AHTTP_/)
         
     | 
| 
      
 64 
     | 
    
         
            +
                    if http_match
         
     | 
| 
      
 65 
     | 
    
         
            +
                      name = http_match.post_match.downcase
         
     | 
| 
      
 66 
     | 
    
         
            +
                      {name => value}
         
     | 
| 
      
 67 
     | 
    
         
            +
                    else
         
     | 
| 
      
 68 
     | 
    
         
            +
                      name = %w(content_type content_length).detect { |name| name.downcase == key.downcase }
         
     | 
| 
      
 69 
     | 
    
         
            +
                      if name
         
     | 
| 
      
 70 
     | 
    
         
            +
                        {name => value}
         
     | 
| 
      
 71 
     | 
    
         
            +
                      end
         
     | 
| 
      
 72 
     | 
    
         
            +
                    end
         
     | 
| 
      
 73 
     | 
    
         
            +
                  end.compact.inject({}, &:update)
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
       59 
75 
     | 
    
         
             
                  data = {
         
     | 
| 
       60 
76 
     | 
    
         
             
                    'request' => {
         
     | 
| 
       61 
77 
     | 
    
         
             
                      'method' => request.request_method,
         
     | 
| 
       62 
78 
     | 
    
         
             
                      'uri' => request_uri.normalize.to_s,
         
     | 
| 
       63 
     | 
    
         
            -
                      ' 
     | 
| 
       64 
     | 
    
         
            -
                      'Content-Type' => request.content_type,
         
     | 
| 
      
 79 
     | 
    
         
            +
                      'headers' => request_headers,
         
     | 
| 
       65 
80 
     | 
    
         
             
                      'remote_addr' => env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"],
         
     | 
| 
       66 
     | 
    
         
            -
                      'User-Agent' => request.user_agent,
         
     | 
| 
       67 
81 
     | 
    
         
             
                      # these come from the OAuthenticator gem/middleware 
         
     | 
| 
       68 
82 
     | 
    
         
             
                      'oauth.authenticated' => env['oauth.authenticated'],
         
     | 
| 
       69 
83 
     | 
    
         
             
                      'oauth.consumer_key' => env['oauth.consumer_key'],
         
     | 
| 
       70 
84 
     | 
    
         
             
                      'oauth.token' => env['oauth.token'],
         
     | 
| 
       71 
85 
     | 
    
         
             
                      # airbrake
         
     | 
| 
       72 
86 
     | 
    
         
             
                      'airbrake.error_id' => env['airbrake.error_id'],
         
     | 
| 
       73 
     | 
    
         
            -
                    }.reject{|k,v| v.nil? },
         
     | 
| 
      
 87 
     | 
    
         
            +
                    }.reject { |k,v| v.nil? },
         
     | 
| 
       74 
88 
     | 
    
         
             
                    'response' => {
         
     | 
| 
       75 
89 
     | 
    
         
             
                      'status' => status,
         
     | 
| 
       76 
     | 
    
         
            -
                      ' 
     | 
| 
       77 
     | 
    
         
            -
                      ' 
     | 
| 
       78 
     | 
    
         
            -
             
     | 
| 
       79 
     | 
    
         
            -
                    }.reject{|k,v| v.nil? },
         
     | 
| 
      
 90 
     | 
    
         
            +
                      'headers' => response_headers,
         
     | 
| 
      
 91 
     | 
    
         
            +
                      'length' => response_headers['Content-Length'] || response_body.to_enum.map(&::Rack::Utils.method(:bytesize)).inject(0, &:+),
         
     | 
| 
      
 92 
     | 
    
         
            +
                    }.reject { |k,v| v.nil? },
         
     | 
| 
       80 
93 
     | 
    
         
             
                    'processing' => {
         
     | 
| 
       81 
94 
     | 
    
         
             
                      'began_at' => began_at.utc.to_i,
         
     | 
| 
       82 
95 
     | 
    
         
             
                      'duration' => now - began_at,
         
     | 
| 
       83 
     | 
    
         
            -
                      'activesupport_tagged_logging_tags' =>  
     | 
| 
       84 
     | 
    
         
            -
                    }.merge(env['request_logger.info'] || {}).merge(Thread.current['request_logger.info'] || {}).reject{|k,v| v.nil? },
         
     | 
| 
      
 96 
     | 
    
         
            +
                      'activesupport_tagged_logging_tags' => log_tags,
         
     | 
| 
      
 97 
     | 
    
         
            +
                    }.merge(env['request_logger.info'] || {}).merge(Thread.current['request_logger.info'] || {}).reject { |k,v| v.nil? },
         
     | 
| 
       85 
98 
     | 
    
         
             
                  }
         
     | 
| 
       86 
99 
     | 
    
         
             
                  ids_from_body = proc do |body_string, content_type|
         
     | 
| 
       87 
100 
     | 
    
         
             
                    media_type = ::Rack::Request.new({'CONTENT_TYPE' => content_type}).media_type
         
     | 
| 
       88 
     | 
    
         
            -
                     
     | 
| 
       89 
     | 
    
         
            -
                       
     | 
| 
       90 
     | 
    
         
            -
             
     | 
| 
      
 101 
     | 
    
         
            +
                    body_object = begin
         
     | 
| 
      
 102 
     | 
    
         
            +
                      if media_type == 'application/json'
         
     | 
| 
      
 103 
     | 
    
         
            +
                        JSON.parse(body_string) rescue nil
         
     | 
| 
      
 104 
     | 
    
         
            +
                      elsif media_type == 'application/x-www-form-urlencoded'
         
     | 
| 
      
 105 
     | 
    
         
            +
                        CGI.parse(body_string).map { |k, vs| {k => vs.last} }.inject({}, &:update)
         
     | 
| 
      
 106 
     | 
    
         
            +
                      end
         
     | 
| 
       91 
107 
     | 
    
         
             
                    end
         
     | 
| 
       92 
108 
     | 
    
         
             
                    if body_object.is_a?(Hash)
         
     | 
| 
       93 
     | 
    
         
            -
                       
     | 
| 
      
 109 
     | 
    
         
            +
                      sep = /(?:\b|\W|_)/
         
     | 
| 
      
 110 
     | 
    
         
            +
                      body_object.reject { |key, value| !(key =~ /#{sep}([ug]u)?id#{sep}/ && value.is_a?(String)) }
         
     | 
| 
       94 
111 
     | 
    
         
             
                    end
         
     | 
| 
       95 
112 
     | 
    
         
             
                  end
         
     | 
| 
       96 
     | 
    
         
            -
                   
     | 
| 
       97 
     | 
    
         
            -
                  data['request']['body_ids'] = request_body_ids if request_body_ids && request_body_ids.any?
         
     | 
| 
       98 
     | 
    
         
            -
                  response_body_string = body.to_enum.to_a.join('')
         
     | 
| 
      
 113 
     | 
    
         
            +
                  response_body_string = response_body.to_enum.to_a.join('')
         
     | 
| 
       99 
114 
     | 
    
         
             
                  if (400..599).include?(status.to_i)
         
     | 
| 
       100 
     | 
    
         
            -
                    # only log  
     | 
| 
      
 115 
     | 
    
         
            +
                    # only log bodies if there was an error (either client or server) 
         
     | 
| 
      
 116 
     | 
    
         
            +
                    data['request']['body'] = request_body
         
     | 
| 
       101 
117 
     | 
    
         
             
                    data['response']['body'] = response_body_string
         
     | 
| 
       102 
118 
     | 
    
         
             
                  else
         
     | 
| 
       103 
119 
     | 
    
         
             
                    # otherwise, log id and uuid fields 
         
     | 
| 
      
 120 
     | 
    
         
            +
                    request_body_ids = ids_from_body.call(request_body, request.content_type)
         
     | 
| 
      
 121 
     | 
    
         
            +
                    data['request']['body_ids'] = request_body_ids if request_body_ids && request_body_ids.any?
         
     | 
| 
       104 
122 
     | 
    
         
             
                    response_body_ids = ids_from_body.call(response_body_string, response.content_type)
         
     | 
| 
       105 
123 
     | 
    
         
             
                    data['response']['body_ids'] = response_body_ids if response_body_ids && response_body_ids.any?
         
     | 
| 
       106 
124 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -112,8 +130,8 @@ module ApiHammer 
     | 
|
| 
       112 
130 
     | 
    
         
             
                    @logger.info json_data
         
     | 
| 
       113 
131 
     | 
    
         
             
                  end
         
     | 
| 
       114 
132 
     | 
    
         
             
                  # do the logging with tags that applied to the request if appropriate 
         
     | 
| 
       115 
     | 
    
         
            -
                  if @logger.respond_to?(:tagged) &&  
     | 
| 
       116 
     | 
    
         
            -
                    @logger.tagged( 
     | 
| 
      
 133 
     | 
    
         
            +
                  if @logger.respond_to?(:tagged) && log_tags
         
     | 
| 
      
 134 
     | 
    
         
            +
                    @logger.tagged(log_tags, &dolog)
         
     | 
| 
       117 
135 
     | 
    
         
             
                  else
         
     | 
| 
       118 
136 
     | 
    
         
             
                    dolog.call
         
     | 
| 
       119 
137 
     | 
    
         
             
                  end
         
     | 
    
        data/lib/api_hammer/version.rb
    CHANGED
    
    
| 
         @@ -104,5 +104,25 @@ describe ApiHammer::RequestLogger do 
     | 
|
| 
       104 
104 
     | 
    
         
             
                  conn.get '/'
         
     | 
| 
       105 
105 
     | 
    
         
             
                  assert_match('[120,120,195]', logio.string)
         
     | 
| 
       106 
106 
     | 
    
         
             
                end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                {
         
     | 
| 
      
 109 
     | 
    
         
            +
                  'application/octet-stream' => false,
         
     | 
| 
      
 110 
     | 
    
         
            +
                  'image/png' => false,
         
     | 
| 
      
 111 
     | 
    
         
            +
                  'image/png; charset=what' => false,
         
     | 
| 
      
 112 
     | 
    
         
            +
                  'text/plain' => true,
         
     | 
| 
      
 113 
     | 
    
         
            +
                  'text/plain; charset=utf-8' => true,
         
     | 
| 
      
 114 
     | 
    
         
            +
                }.each do |content_type, istext|
         
     | 
| 
      
 115 
     | 
    
         
            +
                  it "does #{istext ? '' : 'not'} log body for #{content_type}" do
         
     | 
| 
      
 116 
     | 
    
         
            +
                    app = proc do |env|
         
     | 
| 
      
 117 
     | 
    
         
            +
                      [200, {'Content-Type' => content_type}, ['data go here']]
         
     | 
| 
      
 118 
     | 
    
         
            +
                    end
         
     | 
| 
      
 119 
     | 
    
         
            +
                    conn = Faraday.new do |f|
         
     | 
| 
      
 120 
     | 
    
         
            +
                      f.request :api_hammer_request_logger, logger
         
     | 
| 
      
 121 
     | 
    
         
            +
                      f.use Faraday::Adapter::Rack, app
         
     | 
| 
      
 122 
     | 
    
         
            +
                    end
         
     | 
| 
      
 123 
     | 
    
         
            +
                    conn.get '/'
         
     | 
| 
      
 124 
     | 
    
         
            +
                    assert(logio.string.include?('data go here') == istext)
         
     | 
| 
      
 125 
     | 
    
         
            +
                  end
         
     | 
| 
      
 126 
     | 
    
         
            +
                end
         
     | 
| 
       107 
127 
     | 
    
         
             
              end
         
     | 
| 
       108 
128 
     | 
    
         
             
            end
         
     | 
    
        data/test/request_logger_test.rb
    CHANGED
    
    | 
         @@ -20,4 +20,25 @@ describe ApiHammer::RequestLogger do 
     | 
|
| 
       20 
20 
     | 
    
         
             
                  assert(logio.string.include?(Term::ANSIColor.send(color, status.to_s)))
         
     | 
| 
       21 
21 
     | 
    
         
             
                end
         
     | 
| 
       22 
22 
     | 
    
         
             
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              it 'logs id and uuid (json)' do
         
     | 
| 
      
 25 
     | 
    
         
            +
                body = %q({"uuid": "theuuid", "foo_uuid": "thefoouuid", "id": "theid", "id_for_x": "theidforx", "bar.id": "thebarid", "baz-guid": "bazzz"})
         
     | 
| 
      
 26 
     | 
    
         
            +
                app = ApiHammer::RequestLogger.new(proc { |env| [200, {"Content-Type" => 'application/json; charset=UTF8'}, [body]] }, logger)
         
     | 
| 
      
 27 
     | 
    
         
            +
                app.call(Rack::MockRequest.env_for('/')).last.close
         
     | 
| 
      
 28 
     | 
    
         
            +
                assert_match(%q("body_ids":{"uuid":"theuuid","foo_uuid":"thefoouuid","id":"theid","id_for_x":"theidforx","bar.id":"thebarid","baz-guid":"bazzz"}), logio.string)
         
     | 
| 
      
 29 
     | 
    
         
            +
              end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
              it 'logs id and uuid (form encoded)' do
         
     | 
| 
      
 32 
     | 
    
         
            +
                body = %q(uuid=theuuid&foo_uuid=thefoouuid&id=theid&id_for_x=theidforx&bar.id=thebarid&baz-guid=bazzz)
         
     | 
| 
      
 33 
     | 
    
         
            +
                app = ApiHammer::RequestLogger.new(proc { |env| [200, {"Content-Type" => 'application/x-www-form-urlencoded; charset=UTF8'}, [body]] }, logger)
         
     | 
| 
      
 34 
     | 
    
         
            +
                app.call(Rack::MockRequest.env_for('/')).last.close
         
     | 
| 
      
 35 
     | 
    
         
            +
                assert_match(%q("body_ids":{"uuid":"theuuid","foo_uuid":"thefoouuid","id":"theid","id_for_x":"theidforx","bar.id":"thebarid","baz-guid":"bazzz"}), logio.string)
         
     | 
| 
      
 36 
     | 
    
         
            +
              end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
              it 'logs request and response body on error' do
         
     | 
| 
      
 39 
     | 
    
         
            +
                app = ApiHammer::RequestLogger.new(proc { |env| [400, {}, ['the_response_body']] }, logger)
         
     | 
| 
      
 40 
     | 
    
         
            +
                app.call(Rack::MockRequest.env_for('/', :input => 'the_request_body')).last.close
         
     | 
| 
      
 41 
     | 
    
         
            +
                assert_match(/"request":\{.*"body":"the_request_body"/, logio.string)
         
     | 
| 
      
 42 
     | 
    
         
            +
                assert_match(/"response":\{.*"body":"the_response_body"/, logio.string)
         
     | 
| 
      
 43 
     | 
    
         
            +
              end
         
     | 
| 
       23 
44 
     | 
    
         
             
            end
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: api_hammer
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.5.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Ethan
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire: 
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2014- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2014-08-04 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: rack
         
     |