api_hammer 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 30f42f8b22545e005c29603af5d98d6874e8027b
4
- data.tar.gz: 7257d0076928ecc3c328596243c3b16d465dba6b
3
+ metadata.gz: d674265eaf92948690ef36086f4e6858103e0d92
4
+ data.tar.gz: 00af75f0635b6e4fcd1a373936d2524e74852afb
5
5
  SHA512:
6
- metadata.gz: a3ea4254d06322b584a4b78ec25d3334280c5118406913500cfaf8d2fbc84a9c5478fd6c0d2bfdb15ead42f795b58a9abc61bfa7daff88696195df93cd21f2d3
7
- data.tar.gz: a275cccb8fba41c50c2781bc2423477bec6a688b0d831145ee044889193ab9d70105bf17a138d88cf3380ee4fbbd2216ad1ea42265609bef407729e90680468d
6
+ metadata.gz: 91be2d6f0a923086524397f7aed0c00029db8c049f59029cbf9e9c3d0f4754009b76702af132a2797484a80111a24d13eb7b3017367f668389172dabbd882038
7
+ data.tar.gz: 6d46171d830d5c41794569e2aa73762fa448b72a831e01d2b146cb6e4850998845eb69048a770bb0c75b80048a895b4aa95e98e1e62ce47f40dacb559d03eb49
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ # 0.2.0
2
+ - Rails#unmunged_request_params
3
+ - hc --pretty
4
+ - hc default User-Agent set
5
+ - hc doc improved, middlewares moved lib
6
+ - RequestLogger improved
7
+ - ApiHammer::RailsRequestLogging railtie
8
+ - README improved marginally
9
+
1
10
  # 0.1.0
2
11
  - Object#public_instance_exec
3
12
  - Obect#public_instance_eval
data/README.md CHANGED
@@ -1,3 +1,30 @@
1
1
  # ApiHammer
2
2
 
3
- An API Tool
3
+ a collection of little tools I have used when creating APIs. these are generally too small to have their own
4
+ library, so have been collected here.
5
+
6
+ ## ApiHammer::Rails
7
+
8
+ A module for inclusion in a Rails application controller inheriting from ActionController::Base (Rocketpants
9
+ should also work, but no guarantees).
10
+
11
+ ### #halt
12
+
13
+ You know that pattern in rails, `render(some_stuff) and return`?
14
+
15
+ Don't use it. It's wrong. `and` is used to short-circuit, but you don't actually want your `return` to be conditional on the `render`. Since it is conditional on the return value of `render`, if the return value of `render` is false-ish (`false` or `nil`), you may end end up failing to return. The actual return value of `render` is undocumented. If you follow all the control paths that can affect the return value, last time I looked there were 37 different paths. None of them returns false, but if any control path changes to return false then `render and return` will break.
16
+
17
+ What you really mean is `render(some_stuff); return`, but semicolons are (rightly) frowned upon
18
+ in ruby. Really you should just put the `return` on the next line.
19
+
20
+ Or, use `#halt`, kindly provided by ApiHammer.
21
+
22
+ `ApiHammer#halt` is based on the Sinatra pattern of `throw(:halt, response)`. It uses exceptions because Rails has baked-in exception rescuing (but not, at least as far as I have found, throw catching), but that is hidden away.
23
+
24
+ ### #check_required_params
25
+
26
+ Pass it an parameters which are required, and if they are missing or incorrect it will halt with 422 (not 400 - 400 is wrong).
27
+
28
+ ## Other
29
+
30
+ Various other things. This readme is incomplete and will be updated soon.
data/bin/hc CHANGED
@@ -1,10 +1,16 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ lib = File.expand_path('../lib', File.dirname(__FILE__))
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.any? { |lp| File.expand_path(lp) == File.expand_path(lib) }
5
+
3
6
  require 'rubygems'
4
7
  require 'faraday'
5
8
  require 'logger'
6
9
  require 'yaml'
7
10
 
11
+ require 'api_hammer/faraday/outputter'
12
+ require 'api_hammer/version'
13
+
8
14
  # OPTION PARSER
9
15
 
10
16
  require 'optparse'
@@ -13,6 +19,7 @@ require 'optparse'
13
19
  $options = {
14
20
  :verbose => true,
15
21
  :color => nil,
22
+ :pretty => nil,
16
23
  :no_ssl_verify => false,
17
24
  :headers => {},
18
25
  }
@@ -31,29 +38,32 @@ opt_parser = OptionParser.new do |opts|
31
38
  opts.on("--[no-]color", "Color the output (defaults to color if the output device is a TTY)") do |v|
32
39
  $options[:color] = v
33
40
  end
34
- opts.on("-t", "--content-type CONTENT-TYPE", "Sets the Content-Type header of the request") do |v|
41
+ opts.on("--[no-]pretty", "Format the output prettily if supported. (JSON is supported.) defaults to true.") do |v|
42
+ $options[:pretty] = v
43
+ end
44
+ opts.on("-t", "--content-type CONTENT-TYPE", "Sets the Content-Type header of the request. This defaults to application/json if a body is included.") do |v|
35
45
  $options[:headers]['Content-Type'.downcase] = v
36
46
  end
37
- opts.on("--oauth-token TOKEN") do |token|
47
+ opts.on("--oauth-token TOKEN", "OAuth 1.0 token") do |token|
38
48
  $oauth[:token] = token
39
49
  end
40
- opts.on("--oauth-token-secret TOKEN_SECRET") do |token_secret|
50
+ opts.on("--oauth-token-secret TOKEN_SECRET", "OAuth 1.0 token secret") do |token_secret|
41
51
  $oauth[:token_secret] = token_secret
42
52
  end
43
- opts.on("--oauth-consumer-key CONSUMER_KEY") do |consumer_key|
53
+ opts.on("--oauth-consumer-key CONSUMER_KEY", "OAuth 1.0 consumer key") do |consumer_key|
44
54
  $oauth[:consumer_key] = consumer_key
45
55
  end
46
- opts.on("--oauth-consumer-secret CONSUMER_SECRET") do |consumer_secret|
56
+ opts.on("--oauth-consumer-secret CONSUMER_SECRET", "OAuth 1.0 consumer secret") do |consumer_secret|
47
57
  $oauth[:consumer_secret] = consumer_secret
48
58
  end
49
- opts.on("--oauth-signature-method SIGNATURE_METHOD") do |signature_method|
59
+ opts.on("--oauth-signature-method SIGNATURE_METHOD", "OAuth 1.0 signature method - defaults to HMAC-SHA1") do |signature_method|
50
60
  $oauth[:signature_method] = signature_method
51
61
  end
52
62
 
53
63
  opts.on("--no-ssl-verify", "Disables SSL verification - use cautiously!") do
54
64
  $options[:no_ssl_verify] = true
55
65
  end
56
- opts.on("-H", "--header HEADER", "Set a header") do |header|
66
+ opts.on("-H", "--header HEADER", %q(Set a header, e.g. -H "User-Agent: mycoolapp")) do |header|
57
67
  if header =~ /\A([^:]+):\s*(.*)\z/m # this could be more strictly conformant to rfc, but whatever
58
68
  field_name = $1
59
69
  field_value = $2
@@ -66,126 +76,6 @@ end
66
76
  opt_parser.parse!
67
77
  abort(opt_parser.help) unless (2..3).include?(ARGV.size)
68
78
 
69
- # OUTPUTTERS FOR FARADAY THAT SHOULD MOVE TO A LIB SOMEWHERE
70
-
71
- # outputs the response body to the given output device (defaulting to STDOUT)
72
- class FaradayOutputter < Faraday::Middleware
73
- def initialize(app, outdev=STDOUT)
74
- @app=app
75
- @outdev = outdev
76
- end
77
-
78
- def call(request_env)
79
- @app.call(request_env).on_complete do |response_env|
80
- @outdev.puts(response_env[:body] || '')
81
- end
82
- end
83
- end
84
-
85
- # this is to approximate `curl -v`s output. but it's all faked, whereas curl gives you
86
- # the real text written and read for request and response. whatever, close enough.
87
- class FaradayCurlVOutputter < FaradayOutputter
88
-
89
- # defines a method with the given name, applying coloring defined by any additional arguments.
90
- # if $options[:color] is set, respects that; otherwise, applies color if the output device is a tty.
91
- def self.color(name, *color_args)
92
- define_method(name) do |arg|
93
- if color?
94
- require 'term/ansicolor'
95
- color_args.inject(arg) do |result, color_arg|
96
- Term::ANSIColor.send(color_arg, result)
97
- end
98
- else
99
- arg
100
- end
101
- end
102
- end
103
-
104
- color :info, :intense_yellow
105
- color :info_body, :yellow
106
- color :protocol
107
-
108
- color :request, :intense_cyan
109
- color :request_verb, :bold
110
- color :request_header
111
- color :request_blankline, :intense_cyan, :bold
112
-
113
- color :response, :intense_green
114
- color :response_status, :bold, :green
115
- color :response_header
116
- color :response_blankline, :intense_green, :bold
117
-
118
- def call(request_env)
119
- @outdev.puts "#{info('*')} #{info_body("connect to #{request_env[:url].host} on port #{request_env[:url].port}")}"
120
- @outdev.puts "#{info('*')} #{info_body("getting our SSL on")}" if request_env[:url].scheme=='https'
121
- @outdev.puts "#{request('>')} #{request_verb(request_env[:method].to_s.upcase)} #{request_env[:url].request_uri} #{protocol('HTTP/1.1' || 'or something - TODO')}"
122
- request_env[:request_headers].each do |k, v|
123
- @outdev.puts "#{request('>')} #{request_header(k)}#{request(':')} #{v}"
124
- end
125
- @outdev.puts "#{request_blankline('>')} "
126
- request_body = color_body_by_content_type(request_env[:body], request_env[:request_headers]['Content-Type'])
127
- (request_body || '').split("\n", -1).each do |line|
128
- @outdev.puts "#{request('>')} #{line}"
129
- end
130
- @app.call(request_env).on_complete do |response_env|
131
- @outdev.puts "#{response('<')} #{protocol('HTTP/1.1' || 'or something - TODO')} #{response_status(response_env[:status].to_s)}"
132
- request_env[:response_headers].each do |k, v|
133
- @outdev.puts "#{response('<')} #{response_header(k)}#{response(':')} #{v}"
134
- end
135
- @outdev.puts "#{response_blankline ('<')} "
136
- response_body = color_body_by_content_type(response_env[:body], response_env[:response_headers]['Content-Type'])
137
- (response_body || '').split("\n", -1).each do |line|
138
- @outdev.puts "#{response('<')} #{line}"
139
- end
140
- end
141
- end
142
-
143
- # whether to use color
144
- def color?
145
- $options[:color].nil? ? @outdev.tty? : $options[:color]
146
- end
147
-
148
- # a mapping for each registered CodeRay scanner to the Media Types which represent
149
- # that language. extremely incomplete!
150
- CodeRayForMediaTypes = {
151
- :c => [],
152
- :cpp => [],
153
- :clojure => [],
154
- :css => ['text/css', 'application/css-stylesheet'],
155
- :delphi => [],
156
- :diff => [],
157
- :erb => [],
158
- :groovy => [],
159
- :haml => [],
160
- :html => ['text/html'],
161
- :java => [],
162
- :java_script => ['application/javascript', 'text/javascript', 'application/x-javascript'],
163
- :json => ['application/json'],
164
- :php => [],
165
- :python => ['text/x-python'],
166
- :ruby => [],
167
- :sql => [],
168
- :xml => ['text/xml', 'application/xml', %r(\Aapplication/.*\+xml\z)],
169
- :yaml => [],
170
- }
171
-
172
- # takes a body and a content type; returns the body, with coloring (ansi colors for terminals)
173
- # possibly added, if it's a recognized content type and #color? is true
174
- def color_body_by_content_type(body, content_type)
175
- if body && color?
176
- # kinda hacky way to get the media_type. faraday should supply this ...
177
- require 'rack'
178
- media_type = ::Rack::Request.new({'CONTENT_TYPE' => content_type}).media_type
179
- coderay_scanner = CodeRayForMediaTypes.reject{|k,v| !v.any?{|type| type === media_type} }.keys.first
180
- if coderay_scanner
181
- require 'coderay'
182
- body = CodeRay.scan(body, coderay_scanner).encode(:terminal)
183
- end
184
- end
185
- body
186
- end
187
- end
188
-
189
79
  # CONFIGURE THE FARADAY CONNECTION
190
80
  faraday_options = {}
191
81
  if $options[:no_ssl_verify]
@@ -198,7 +88,7 @@ connection = Faraday.new(faraday_options) do |builder|
198
88
  OAuthenticator::FaradaySigner
199
89
  builder.use OAuthenticator::FaradaySigner, $oauth
200
90
  end
201
- builder.use $options[:verbose] ? FaradayCurlVOutputter : FaradayOutputter
91
+ builder.use($options[:verbose] ? FaradayCurlVOutputter : FaradayOutputter, $options)
202
92
  builder.adapter Faraday.default_adapter
203
93
  end
204
94
 
@@ -215,6 +105,10 @@ if body && !headers['Content-Type'.downcase]
215
105
  headers['Content-Type'.downcase] = 'application/json'
216
106
  end
217
107
 
108
+ unless headers.keys.any? { |key| key.downcase == 'user-agent' }
109
+ headers['User-Agent'] = "ApiHammer #{ApiHammer::VERSION} hc"
110
+ end
111
+
218
112
  # OH LOOK IT'S FINALLY ACTUALLY CONNECTING TO SOMETHING
219
113
 
220
114
  response = connection.run_request(httpmethod.downcase.to_sym, url, body, headers)
@@ -0,0 +1,137 @@
1
+ require 'faraday'
2
+ require 'rack'
3
+
4
+ # outputs the response body to the given output device (defaulting to STDOUT)
5
+ class FaradayOutputter < Faraday::Middleware
6
+ def initialize(app, options={})
7
+ @app=app
8
+ @options = options
9
+ @outdev = @options[:outdev] || STDOUT
10
+ end
11
+
12
+ def call(request_env)
13
+ @app.call(request_env).on_complete do |response_env|
14
+ @outdev.puts(response_env[:body] || '')
15
+ end
16
+ end
17
+ end
18
+
19
+ # this is to approximate `curl -v`s output. but it's all faked, whereas curl gives you
20
+ # the real text written and read for request and response. whatever, close enough.
21
+ class FaradayCurlVOutputter < FaradayOutputter
22
+
23
+ # defines a method with the given name, applying coloring defined by any additional arguments.
24
+ # if @options[:color] is set, respects that; otherwise, applies color if the output device is a tty.
25
+ def self.color(name, *color_args)
26
+ define_method(name) do |arg|
27
+ if color?
28
+ require 'term/ansicolor'
29
+ color_args.inject(arg) do |result, color_arg|
30
+ Term::ANSIColor.send(color_arg, result)
31
+ end
32
+ else
33
+ arg
34
+ end
35
+ end
36
+ end
37
+
38
+ color :info, :intense_yellow
39
+ color :info_body, :yellow
40
+ color :protocol
41
+
42
+ color :request, :intense_cyan
43
+ color :request_verb, :bold
44
+ color :request_header
45
+ color :request_blankline, :intense_cyan, :bold
46
+
47
+ color :response, :intense_green
48
+ color :response_status, :bold, :green
49
+ color :response_header
50
+ color :response_blankline, :intense_green, :bold
51
+
52
+ def call(request_env)
53
+ @outdev.puts "#{info('*')} #{info_body("connect to #{request_env[:url].host} on port #{request_env[:url].port}")}"
54
+ @outdev.puts "#{info('*')} #{info_body("getting our SSL on")}" if request_env[:url].scheme=='https'
55
+ @outdev.puts "#{request('>')} #{request_verb(request_env[:method].to_s.upcase)} #{request_env[:url].request_uri} #{protocol("HTTP/#{Net::HTTP::HTTPVersion}")}"
56
+ request_env[:request_headers].each do |k, v|
57
+ @outdev.puts "#{request('>')} #{request_header(k)}#{request(':')} #{v}"
58
+ end
59
+ @outdev.puts "#{request_blankline('>')} "
60
+ request_body = alter_body_by_content_type(request_env[:body], request_env[:request_headers]['Content-Type'])
61
+ (request_body || '').split("\n", -1).each do |line|
62
+ @outdev.puts "#{request('>')} #{line}"
63
+ end
64
+ @app.call(request_env).on_complete do |response_env|
65
+ @outdev.puts "#{response('<')} #{protocol('HTTP/1.1' || 'or something - TODO')} #{response_status(response_env[:status].to_s)}"
66
+ request_env[:response_headers].each do |k, v|
67
+ @outdev.puts "#{response('<')} #{response_header(k)}#{response(':')} #{v}"
68
+ end
69
+ @outdev.puts "#{response_blankline ('<')} "
70
+ response_body = alter_body_by_content_type(response_env[:body], response_env[:response_headers]['Content-Type'])
71
+ (response_body || '').split("\n", -1).each do |line|
72
+ @outdev.puts "#{response('<')} #{line}"
73
+ end
74
+ end
75
+ end
76
+
77
+ def pretty?
78
+ @options[:pretty].nil? ? true : @options[:pretty]
79
+ end
80
+
81
+ # whether to use color
82
+ def color?
83
+ @options[:color].nil? ? @outdev.tty? : @options[:color]
84
+ end
85
+
86
+ # a mapping for each registered CodeRay scanner to the Media Types which represent
87
+ # that language. extremely incomplete!
88
+ CodeRayForMediaTypes = {
89
+ :c => [],
90
+ :cpp => [],
91
+ :clojure => [],
92
+ :css => ['text/css', 'application/css-stylesheet'],
93
+ :delphi => [],
94
+ :diff => [],
95
+ :erb => [],
96
+ :groovy => [],
97
+ :haml => [],
98
+ :html => ['text/html'],
99
+ :java => [],
100
+ :java_script => ['application/javascript', 'text/javascript', 'application/x-javascript'],
101
+ :json => ['application/json'],
102
+ :php => [],
103
+ :python => ['text/x-python'],
104
+ :ruby => [],
105
+ :sql => [],
106
+ :xml => ['text/xml', 'application/xml', %r(\Aapplication/.*\+xml\z)],
107
+ :yaml => [],
108
+ }
109
+
110
+ # takes a body and a content type; returns the body, altered according to options.
111
+ #
112
+ # - with coloring (ansi colors for terminals) possibly added, if it's a recognized content type and
113
+ # #color? is true
114
+ # - formatted prettily if #pretty? is true
115
+ def alter_body_by_content_type(body, content_type)
116
+ return body unless body.is_a?(String)
117
+ media_type = ::Rack::Request.new({'CONTENT_TYPE' => content_type}).media_type
118
+ if pretty?
119
+ case media_type
120
+ when 'application/json'
121
+ require 'json'
122
+ begin
123
+ body = JSON.pretty_generate(JSON.parse(body))
124
+ rescue JSON::ParserError
125
+ end
126
+ end
127
+ end
128
+ if color?
129
+ coderay_scanner = CodeRayForMediaTypes.reject{|k,v| !v.any?{|type| type === media_type} }.keys.first
130
+ if coderay_scanner
131
+ require 'coderay'
132
+ body = CodeRay.scan(body, coderay_scanner).encode(:terminal)
133
+ end
134
+ end
135
+ body
136
+ end
137
+ end
@@ -1,5 +1,6 @@
1
1
  require 'api_hammer/halt'
2
2
  require 'api_hammer/check_required_params'
3
+ require 'api_hammer/unmunged_request_params'
3
4
 
4
5
  module ApiHammer::Rails
5
6
  def self.included(klass)
@@ -0,0 +1,59 @@
1
+ require 'api_hammer'
2
+ require 'active_support/log_subscriber'
3
+
4
+ require 'rails/rack/log_tailer'
5
+ # fix up this class to tail the log when the body is closed rather than when its own #call is done.
6
+ module Rails
7
+ module Rack
8
+ class LogTailer
9
+ def call(env)
10
+ status, headers, body = @app.call(env)
11
+ body_proxy = ::Rack::BodyProxy.new(body) { tail! }
12
+ [status, headers, body_proxy]
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ module ApiHammer
19
+ class RequestLogSubscriber < ActiveSupport::LogSubscriber
20
+ def process_action(event)
21
+ if event.payload[:request_env]
22
+ info = (event.payload[:request_env]['request_logger.info'] ||= {})
23
+ else
24
+ # if an exception occurs in the action, append_info_to_payload isn't called and
25
+ # event.payload[:request_env] doesn't get set. fall back to use Thread.current.
26
+ info = (Thread.current['request_logger.info'] ||= {})
27
+ end
28
+ info.update(event.payload.slice(:controller, :action, :exception, :format, :formats, :view_runtime, :db_runtime))
29
+ info.update(:transaction_id => event.transaction_id)
30
+ info.update(event.payload['request_logger.info']) if event.payload['request_logger.info']
31
+ end
32
+ end
33
+ end
34
+
35
+ module AddRequestToPayload
36
+ def append_info_to_payload(payload)
37
+ super
38
+ payload[:request_env] = request.env
39
+ end
40
+ end
41
+
42
+ module ApiHammer
43
+ class RailsRequestLogging < ::Rails::Railtie
44
+ initializer :api_hammer_request_logging do |app|
45
+ # use the bits we want from Lograge.setup, disabling existing active* log things.
46
+ # but don't actually enable lograge.
47
+ require 'lograge'
48
+ require 'lograge/rails_ext/rack/logger'
49
+ app.config.action_dispatch.rack_cache[:verbose] = false if app.config.action_dispatch.rack_cache
50
+ Lograge.remove_existing_log_subscriptions
51
+
52
+ ApiHammer::RequestLogSubscriber.attach_to :action_controller
53
+
54
+ app.config.middleware.insert_after(::Rails::Rack::Logger, ApiHammer::RequestLogger, ::Rails.logger)
55
+
56
+ ActionController::Base.send(:include, AddRequestToPayload)
57
+ end
58
+ end
59
+ end
@@ -23,10 +23,13 @@ module ApiHammer
23
23
  @request_body = env["rack.input"].read
24
24
  env["rack.input"].rewind
25
25
 
26
- status, header, body = @app.call(env)
27
- header = ::Rack::Utils::HeaderHash.new(header)
28
- body_proxy = ::Rack::BodyProxy.new(body) { log(env, status, header, began_at, body) }
29
- [status, header, body_proxy]
26
+ log_tags = Thread.current[:activesupport_tagged_logging_tags]
27
+ @log_tags = log_tags.dup if log_tags
28
+
29
+ status, headers, body = @app.call(env)
30
+ headers = ::Rack::Utils::HeaderHash.new(headers)
31
+ body_proxy = ::Rack::BodyProxy.new(body) { log(env, status, headers, began_at, body) }
32
+ [status, headers, body_proxy]
30
33
  end
31
34
 
32
35
  def log(env, status, headers, began_at, body)
@@ -53,7 +56,6 @@ module ApiHammer
53
56
  :white
54
57
  end
55
58
  status_s = bold(send(status_color, status.to_s))
56
- @logger.info "#{status_s} : #{bold(intense_cyan(request.request_method))} #{intense_cyan(request_uri.normalize)}"
57
59
  data = {
58
60
  'request' => {
59
61
  'method' => request.request_method,
@@ -63,6 +65,12 @@ module ApiHammer
63
65
  'remote_addr' => env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"],
64
66
  'User-Agent' => request.user_agent,
65
67
  'body' => @request_body,
68
+ # these come from the OAuthenticator gem/middleware
69
+ 'oauth.authenticated' => env['oauth.authenticated'],
70
+ 'oauth.consumer_key' => env['oauth.consumer_key'],
71
+ 'oauth.token' => env['oauth.token'],
72
+ # airbrake
73
+ 'airbrake.error_id' => env['airbrake.error_id'],
66
74
  }.reject{|k,v| v.nil? },
67
75
  'response' => {
68
76
  'status' => status,
@@ -70,12 +78,24 @@ module ApiHammer
70
78
  'Location' => response.location,
71
79
  'Content-Type' => response.content_type,
72
80
  }.reject{|k,v| v.nil? },
73
- 'began_at' => began_at.utc.to_i,
74
- 'duration' => now - began_at,
81
+ 'processing' => {
82
+ 'began_at' => began_at.utc.to_i,
83
+ 'duration' => now - began_at,
84
+ 'activesupport_tagged_logging_tags' => @log_tags,
85
+ }.merge(env['request_logger.info'] || {}).merge(Thread.current['request_logger.info'] || {}).reject{|k,v| v.nil? },
75
86
  }
87
+ Thread.current['request_logger.info'] = nil
76
88
  json_data = JSON.dump(data)
77
- @logger.debug json_data
78
- $ZMQ_LOGGER.log json_data if defined?($ZMQ_LOGGER)
89
+ dolog = proc do
90
+ @logger.info "#{status_s} : #{bold(intense_cyan(request.request_method))} #{intense_cyan(request_uri.normalize)}"
91
+ @logger.info json_data
92
+ end
93
+ # do the logging with tags that applied to the request if appropriate
94
+ if @logger.respond_to?(:tagged) && @log_tags
95
+ @logger.tagged(@log_tags, &dolog)
96
+ else
97
+ dolog.call
98
+ end
79
99
  end
80
100
  end
81
101
  end
@@ -0,0 +1,26 @@
1
+ module ApiHammer::Rails
2
+ # request parameters (not query parameters) without the nil/empty array munging that rails does
3
+ def unmunged_request_params
4
+ # Thread.exclusive is not optimal but we need to ensure that any other params parsing occurring in other
5
+ # threads is not affected by disabling munging
6
+ #
7
+ # TODO when we are on a rails which has ActionDispatch::Request::Utils.perform_deep_munge, use that instead
8
+ # of clobbering methods
9
+ @unmunged_params ||= Thread.exclusive do
10
+ unless ActionDispatch::Request.method_defined?(:real_deep_munge)
11
+ ActionDispatch::Request.send(:alias_method, :real_deep_munge, :deep_munge)
12
+ end
13
+ ActionDispatch::Request.send(:define_method, :deep_munge) { |hash| hash }
14
+ begin
15
+ unmunged_params = nil
16
+ newenv = request.env.merge('action_dispatch.request.request_parameters' => nil)
17
+ ActionDispatch::ParamsParser.new(proc do |env|
18
+ unmunged_params = env['action_dispatch.request.request_parameters']
19
+ end).call(newenv)
20
+ unmunged_params || ActionDispatch::Request.new(newenv).request_parameters
21
+ ensure
22
+ ActionDispatch::Request.send(:alias_method, :deep_munge, :real_deep_munge)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module ApiHammer
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  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.1.0
4
+ version: 0.2.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-05-08 00:00:00.000000000 Z
11
+ date: 2014-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: term-ansicolor
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -168,15 +182,18 @@ files:
168
182
  - bin/hc
169
183
  - lib/api_hammer.rb
170
184
  - lib/api_hammer/check_required_params.rb
185
+ - lib/api_hammer/faraday/outputter.rb
171
186
  - lib/api_hammer/halt.rb
172
187
  - lib/api_hammer/public_instance_exec.rb
173
188
  - lib/api_hammer/rails.rb
189
+ - lib/api_hammer/rails_request_logging.rb
174
190
  - lib/api_hammer/request_logger.rb
175
191
  - lib/api_hammer/show_text_exceptions.rb
176
192
  - lib/api_hammer/tasks.rb
177
193
  - lib/api_hammer/tasks/cucumber_pretty.rb
178
194
  - lib/api_hammer/tasks/gem_available_updates.rb
179
195
  - lib/api_hammer/trailing_newline.rb
196
+ - lib/api_hammer/unmunged_request_params.rb
180
197
  - lib/api_hammer/version.rb
181
198
  - lib/api_hammer/weblink.rb
182
199
  - test/check_required_params_test.rb