api_hammer 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +28 -1
- data/bin/hc +22 -128
- data/lib/api_hammer/faraday/outputter.rb +137 -0
- data/lib/api_hammer/rails.rb +1 -0
- data/lib/api_hammer/rails_request_logging.rb +59 -0
- data/lib/api_hammer/request_logger.rb +29 -9
- data/lib/api_hammer/unmunged_request_params.rb +26 -0
- data/lib/api_hammer/version.rb +1 -1
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d674265eaf92948690ef36086f4e6858103e0d92
|
4
|
+
data.tar.gz: 00af75f0635b6e4fcd1a373936d2524e74852afb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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("
|
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",
|
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
|
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
|
data/lib/api_hammer/rails.rb
CHANGED
@@ -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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
'
|
74
|
-
|
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
|
-
|
78
|
-
|
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
|
data/lib/api_hammer/version.rb
CHANGED
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.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-
|
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
|