httplog 1.3.3 → 1.4.2
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 +8 -0
- data/README.md +12 -2
- data/httplog.gemspec +1 -0
- data/lib/httplog/adapters/ethon.rb +2 -1
- data/lib/httplog/adapters/excon.rb +2 -1
- data/lib/httplog/adapters/http.rb +5 -3
- data/lib/httplog/adapters/httpclient.rb +6 -4
- data/lib/httplog/adapters/net_http.rb +2 -1
- data/lib/httplog/adapters/patron.rb +2 -1
- data/lib/httplog/configuration.rb +26 -22
- data/lib/httplog/http_log.rb +89 -18
- data/lib/httplog/version.rb +1 -1
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5332ba82035e300b5010a5cbcb125d9c90e083a405c43d1be8df1d626fc40fe1
|
4
|
+
data.tar.gz: 9692d53da3306a12bb14128f8365a9d7524fe6d443474c3dbdc11c21a8f3f6e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7829d7f38262b59ac69ae08e638aad00315322025fed502abf50e4ca468ea4286473c54984277ec98d436ff04fc86157ff38e5e529ec6ee8a9bef5a362f80e48
|
7
|
+
data.tar.gz: 4d0ccb0d024d21de6dcc9d116353810cf2e8adbc936c9b9ecbd38887931851555c19eb2756b7f43f298b9f7d6cc855322d75f4c473d813c35cc574c63cdb9ae0
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## 1.4.1 - 2020-02-08 - YANKED
|
2
|
+
|
3
|
+
* [#91](https://github.com/trusche/httplog/pull/91) Fixed bug returning empty response with HTTP gem
|
4
|
+
|
5
|
+
## 1.4.0 - 2020-01-19 - YANKED
|
6
|
+
|
7
|
+
* [#85](https://github.com/trusche/httplog/pull/85) Parse JSON response and apply deep masking
|
8
|
+
|
1
9
|
## 1.3.3 - 2019-11-14
|
2
10
|
|
3
11
|
* [#83](https://github.com/trusche/httplog/pull/83) Support for graylog
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
## httplog
|
2
2
|
|
3
|
+
**VERSION 1.4.0 and 1.4.1 HAVE BEEN YANKED** from rubygems.org due to [this issue](https://github.com/trusche/httplog/issues/89), please update to version 1.4.2. Sorry about that...
|
4
|
+
|
3
5
|
[](http://badge.fury.io/rb/httplog) [](https://travis-ci.org/trusche/httplog) [](https://codeclimate.com/github/trusche/httplog)
|
4
6
|
[](https://img.shields.io/github/release/trusche/httplog.svg)
|
5
7
|
|
@@ -86,7 +88,15 @@ HttpLog.configure do |config|
|
|
86
88
|
config.url_whitelist_pattern = nil
|
87
89
|
config.url_blacklist_pattern = nil
|
88
90
|
|
89
|
-
# Mask
|
91
|
+
# Mask sensitive information in request and response JSON data.
|
92
|
+
# Enable global JSON masking by setting the parameter to `/.*/`
|
93
|
+
config.url_masked_body_pattern = nil
|
94
|
+
|
95
|
+
# You can specify any custom JSON serializer that implements `load` and `dump` class methods
|
96
|
+
# to parse JSON responses
|
97
|
+
config.json_parser = JSON
|
98
|
+
|
99
|
+
# Mask the values of sensitive request parameters
|
90
100
|
config.filter_parameters = %w[password]
|
91
101
|
end
|
92
102
|
```
|
@@ -121,7 +131,7 @@ HttpLog.configure do |config|
|
|
121
131
|
end
|
122
132
|
```
|
123
133
|
|
124
|
-
If you use Graylog and want to use its search features such as "
|
134
|
+
If you use Graylog and want to use its search features such as "benchmark:>1 AND method:PUT",
|
125
135
|
you can use this configuration:
|
126
136
|
|
127
137
|
```ruby
|
data/httplog.gemspec
CHANGED
@@ -37,6 +37,7 @@ Gem::Specification.new do |gem|
|
|
37
37
|
gem.add_development_dependency 'rspec', ['~> 3.7']
|
38
38
|
gem.add_development_dependency 'simplecov', ['~> 0.15']
|
39
39
|
gem.add_development_dependency 'thin', ['~> 1.7']
|
40
|
+
gem.add_development_dependency 'oj', ['>= 3.9.2']
|
40
41
|
|
41
42
|
gem.add_dependency 'rack', ['>= 1.0']
|
42
43
|
gem.add_dependency 'rainbow', ['>= 2.0.0']
|
@@ -39,7 +39,8 @@ if defined?(Ethon)
|
|
39
39
|
response_headers: headers.map { |header| header.split(/:\s/) }.to_h,
|
40
40
|
benchmark: bm,
|
41
41
|
encoding: encoding,
|
42
|
-
content_type: content_type
|
42
|
+
content_type: content_type,
|
43
|
+
mask_body: HttpLog.masked_body_url?(url)
|
43
44
|
)
|
44
45
|
return_code
|
45
46
|
end
|
@@ -12,7 +12,8 @@ if defined?(::HTTP::Client) && defined?(::HTTP::Connection)
|
|
12
12
|
@response = send(orig_request_method, req, options)
|
13
13
|
end
|
14
14
|
|
15
|
-
|
15
|
+
uri = req.uri
|
16
|
+
if HttpLog.url_approved?(uri)
|
16
17
|
body = if defined?(::HTTP::Request::Body)
|
17
18
|
req.body.respond_to?(:source) ? req.body.source : req.body.instance_variable_get(:@body)
|
18
19
|
else
|
@@ -21,7 +22,7 @@ if defined?(::HTTP::Client) && defined?(::HTTP::Connection)
|
|
21
22
|
|
22
23
|
HttpLog.call(
|
23
24
|
method: req.verb,
|
24
|
-
url:
|
25
|
+
url: uri,
|
25
26
|
request_body: body,
|
26
27
|
request_headers: req.headers,
|
27
28
|
response_code: @response.code,
|
@@ -29,7 +30,8 @@ if defined?(::HTTP::Client) && defined?(::HTTP::Connection)
|
|
29
30
|
response_headers: @response.headers,
|
30
31
|
benchmark: bm,
|
31
32
|
encoding: @response.headers['Content-Encoding'],
|
32
|
-
content_type: @response.headers['Content-Type']
|
33
|
+
content_type: @response.headers['Content-Type'],
|
34
|
+
mask_body: HttpLog.masked_body_url?(uri)
|
33
35
|
)
|
34
36
|
|
35
37
|
body.rewind if body.respond_to?(:rewind)
|
@@ -16,12 +16,13 @@ if defined?(::HTTPClient)
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
|
19
|
+
request_uri = req.header.request_uri
|
20
|
+
if HttpLog.url_approved?(request_uri)
|
20
21
|
res = conn.pop
|
21
22
|
|
22
23
|
HttpLog.call(
|
23
24
|
method: req.header.request_method,
|
24
|
-
url:
|
25
|
+
url: request_uri,
|
25
26
|
request_body: req.body,
|
26
27
|
request_headers: req.headers,
|
27
28
|
response_code: res.status_code,
|
@@ -29,7 +30,8 @@ if defined?(::HTTPClient)
|
|
29
30
|
response_headers: res.headers,
|
30
31
|
benchmark: bm,
|
31
32
|
encoding: res.headers['Content-Encoding'],
|
32
|
-
content_type: res.headers['Content-Type']
|
33
|
+
content_type: res.headers['Content-Type'],
|
34
|
+
mask_body: HttpLog.masked_body_url?(request_uri)
|
33
35
|
)
|
34
36
|
conn.push(res)
|
35
37
|
end
|
@@ -41,7 +43,7 @@ if defined?(::HTTPClient)
|
|
41
43
|
alias orig_create_socket create_socket
|
42
44
|
|
43
45
|
# up to version 2.6, the method signature is `create_socket(site)`; after that,
|
44
|
-
# it's `create_socket(
|
46
|
+
# it's `create_socket(host, port)`
|
45
47
|
if instance_method(:create_socket).arity == 1
|
46
48
|
def create_socket(site)
|
47
49
|
if HttpLog.url_approved?("#{site.host}:#{site.port}")
|
@@ -23,7 +23,8 @@ module Net
|
|
23
23
|
response_headers: @response.each_header.collect,
|
24
24
|
benchmark: bm,
|
25
25
|
encoding: @response['Content-Encoding'],
|
26
|
-
content_type: @response['Content-Type']
|
26
|
+
content_type: @response['Content-Type'],
|
27
|
+
mask_body: HttpLog.masked_body_url?(url)
|
27
28
|
)
|
28
29
|
end
|
29
30
|
|
@@ -20,7 +20,8 @@ if defined?(Patron)
|
|
20
20
|
response_headers: @response.headers,
|
21
21
|
benchmark: bm,
|
22
22
|
encoding: @response.headers['Content-Encoding'],
|
23
|
-
content_type: @response.headers['Content-Type']
|
23
|
+
content_type: @response.headers['Content-Type'],
|
24
|
+
mask_body: HttpLog.masked_body_url?(url)
|
24
25
|
)
|
25
26
|
end
|
26
27
|
|
@@ -19,35 +19,39 @@ module HttpLog
|
|
19
19
|
:log_benchmark,
|
20
20
|
:url_whitelist_pattern,
|
21
21
|
:url_blacklist_pattern,
|
22
|
+
:url_masked_body_pattern,
|
22
23
|
:color,
|
23
24
|
:prefix_data_lines,
|
24
25
|
:prefix_response_lines,
|
25
26
|
:prefix_line_numbers,
|
27
|
+
:json_parser,
|
26
28
|
:filter_parameters
|
27
29
|
|
28
30
|
def initialize
|
29
|
-
@enabled
|
30
|
-
@compact_log
|
31
|
-
@json_log
|
32
|
-
@graylog
|
33
|
-
@logger
|
34
|
-
@logger_method
|
35
|
-
@severity
|
36
|
-
@prefix
|
37
|
-
@log_connect
|
38
|
-
@log_request
|
39
|
-
@log_headers
|
40
|
-
@log_data
|
41
|
-
@log_status
|
42
|
-
@log_response
|
43
|
-
@log_benchmark
|
44
|
-
@url_whitelist_pattern
|
45
|
-
@url_blacklist_pattern
|
46
|
-
@
|
47
|
-
@
|
48
|
-
@
|
49
|
-
@
|
50
|
-
@
|
31
|
+
@enabled = true
|
32
|
+
@compact_log = false
|
33
|
+
@json_log = false
|
34
|
+
@graylog = false
|
35
|
+
@logger = Logger.new($stdout)
|
36
|
+
@logger_method = :log
|
37
|
+
@severity = Logger::Severity::DEBUG
|
38
|
+
@prefix = LOG_PREFIX
|
39
|
+
@log_connect = true
|
40
|
+
@log_request = true
|
41
|
+
@log_headers = false
|
42
|
+
@log_data = true
|
43
|
+
@log_status = true
|
44
|
+
@log_response = true
|
45
|
+
@log_benchmark = true
|
46
|
+
@url_whitelist_pattern = nil
|
47
|
+
@url_blacklist_pattern = nil
|
48
|
+
@url_masked_body_pattern = nil
|
49
|
+
@color = false
|
50
|
+
@prefix_data_lines = false
|
51
|
+
@prefix_response_lines = false
|
52
|
+
@prefix_line_numbers = false
|
53
|
+
@json_parser = JSON
|
54
|
+
@filter_parameters = []
|
51
55
|
end
|
52
56
|
end
|
53
57
|
end
|
data/lib/httplog/http_log.rb
CHANGED
@@ -29,6 +29,7 @@ module HttpLog
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def call(options = {})
|
32
|
+
parse_request(options)
|
32
33
|
if config.json_log
|
33
34
|
log_json(options)
|
34
35
|
elsif config.graylog
|
@@ -42,7 +43,7 @@ module HttpLog
|
|
42
43
|
HttpLog.log_status(options[:response_code])
|
43
44
|
HttpLog.log_benchmark(options[:benchmark])
|
44
45
|
HttpLog.log_headers(options[:response_headers])
|
45
|
-
HttpLog.log_body(options[:response_body], options[:encoding], options[:content_type])
|
46
|
+
HttpLog.log_body(options[:response_body], options[:mask_body], options[:encoding], options[:content_type])
|
46
47
|
end
|
47
48
|
end
|
48
49
|
|
@@ -52,10 +53,14 @@ module HttpLog
|
|
52
53
|
!config.url_whitelist_pattern || url.to_s.match(config.url_whitelist_pattern)
|
53
54
|
end
|
54
55
|
|
56
|
+
def masked_body_url?(url)
|
57
|
+
config.filter_parameters.any? && config.url_masked_body_pattern && url.to_s.match(config.url_masked_body_pattern)
|
58
|
+
end
|
59
|
+
|
55
60
|
def log(msg)
|
56
61
|
return unless config.enabled
|
57
62
|
|
58
|
-
config.logger.public_send(config.logger_method, config.severity, colorize(prefix + msg))
|
63
|
+
config.logger.public_send(config.logger_method, config.severity, colorize(prefix + msg.to_s))
|
59
64
|
end
|
60
65
|
|
61
66
|
def log_connection(host, port = nil)
|
@@ -91,10 +96,10 @@ module HttpLog
|
|
91
96
|
log("Benchmark: #{seconds.to_f.round(6)} seconds")
|
92
97
|
end
|
93
98
|
|
94
|
-
def log_body(body, encoding = nil, content_type = nil)
|
99
|
+
def log_body(body, mask_body, encoding = nil, content_type = nil)
|
95
100
|
return unless config.log_response
|
96
101
|
|
97
|
-
data = parse_body(body, encoding, content_type)
|
102
|
+
data = parse_body(body, mask_body, encoding, content_type)
|
98
103
|
|
99
104
|
if config.prefix_response_lines
|
100
105
|
log('Response:')
|
@@ -106,10 +111,9 @@ module HttpLog
|
|
106
111
|
log("Response: #{e.message}")
|
107
112
|
end
|
108
113
|
|
109
|
-
def parse_body(body, encoding, content_type)
|
110
|
-
unless text_based?(content_type)
|
111
|
-
|
112
|
-
end
|
114
|
+
def parse_body(body, mask_body, encoding, content_type)
|
115
|
+
raise BodyParsingError, "(not showing binary data)" unless text_based?(content_type)
|
116
|
+
|
113
117
|
|
114
118
|
if body.is_a?(Net::ReadAdapter)
|
115
119
|
# open-uri wraps the response in a Net::ReadAdapter that defers reading
|
@@ -117,6 +121,9 @@ module HttpLog
|
|
117
121
|
raise BodyParsingError, '(not available yet)'
|
118
122
|
end
|
119
123
|
|
124
|
+
body = body.to_s if defined?(HTTP::Response::Body) && body.is_a?(HTTP::Response::Body)
|
125
|
+
body = body.dup
|
126
|
+
|
120
127
|
if encoding =~ /gzip/ && body && !body.empty?
|
121
128
|
begin
|
122
129
|
sio = StringIO.new(body.to_s)
|
@@ -127,14 +134,25 @@ module HttpLog
|
|
127
134
|
end
|
128
135
|
end
|
129
136
|
|
130
|
-
utf_encoded(body.to_s, content_type)
|
137
|
+
result = utf_encoded(body.to_s, content_type)
|
138
|
+
|
139
|
+
if mask_body && body && !body.empty?
|
140
|
+
if content_type =~ /json/
|
141
|
+
result = begin
|
142
|
+
masked_data config.json_parser.load(result)
|
143
|
+
rescue => e
|
144
|
+
'Failed to mask response body: ' + e.message
|
145
|
+
end
|
146
|
+
else
|
147
|
+
result = masked(result)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
result
|
131
151
|
end
|
132
152
|
|
133
153
|
def log_data(data)
|
134
154
|
return unless config.log_data
|
135
155
|
|
136
|
-
data = utf_encoded(masked(data.dup).to_s) unless data.nil?
|
137
|
-
|
138
156
|
if config.prefix_data_lines
|
139
157
|
log('Data:')
|
140
158
|
log_data_lines(data)
|
@@ -172,22 +190,43 @@ module HttpLog
|
|
172
190
|
def log_json(data = {})
|
173
191
|
return unless config.json_log
|
174
192
|
|
175
|
-
log(
|
193
|
+
log(
|
194
|
+
begin
|
195
|
+
dump_json(data)
|
196
|
+
rescue
|
197
|
+
data[:response_body] = "#{config.json_parser} dump failed"
|
198
|
+
data[:request_body] = "#{config.json_parser} dump failed"
|
199
|
+
dump_json(data)
|
200
|
+
end
|
201
|
+
)
|
202
|
+
end
|
203
|
+
|
204
|
+
def dump_json(data)
|
205
|
+
config.json_parser.dump(json_payload(data))
|
176
206
|
end
|
177
207
|
|
178
|
-
def log_graylog(data
|
208
|
+
def log_graylog(data)
|
179
209
|
result = json_payload(data)
|
180
210
|
|
181
|
-
result[:
|
182
|
-
|
183
|
-
|
211
|
+
result[:short_message] = result.delete(:url)
|
212
|
+
begin
|
213
|
+
send_to_graylog result
|
214
|
+
rescue
|
215
|
+
result[:response_body] = 'Graylog JSON dump failed'
|
216
|
+
result[:request_body] = 'Graylog JSON dump failed'
|
217
|
+
send_to_graylog result
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def send_to_graylog data
|
222
|
+
config.logger.public_send(config.logger_method, config.severity, data)
|
184
223
|
end
|
185
224
|
|
186
225
|
def json_payload(data = {})
|
187
226
|
data[:response_code] = transform_response_code(data[:response_code]) if data[:response_code].is_a?(Symbol)
|
188
227
|
|
189
228
|
parsed_body = begin
|
190
|
-
parse_body(data[:response_body], data[:encoding], data[:content_type])
|
229
|
+
parse_body(data[:response_body], data[:mask_body], data[:encoding], data[:content_type])
|
191
230
|
rescue BodyParsingError => e
|
192
231
|
e.message
|
193
232
|
end
|
@@ -203,7 +242,7 @@ module HttpLog
|
|
203
242
|
{
|
204
243
|
method: data[:method].to_s.upcase,
|
205
244
|
url: masked(data[:url]),
|
206
|
-
request_body:
|
245
|
+
request_body: data[:request_body],
|
207
246
|
request_headers: masked(data[:request_headers].to_h),
|
208
247
|
response_code: data[:response_code].to_i,
|
209
248
|
response_body: parsed_body,
|
@@ -236,6 +275,38 @@ module HttpLog
|
|
236
275
|
end
|
237
276
|
end
|
238
277
|
|
278
|
+
def parse_request(options)
|
279
|
+
return if options[:request_body].nil?
|
280
|
+
|
281
|
+
# Downcase content-type and content-encoding because ::HTTP returns "Content-Type" and "Content-Encoding"
|
282
|
+
headers = options[:request_headers].find_all do |header, _|
|
283
|
+
%w[content-type Content-Type content-encoding Content-Encoding].include? header
|
284
|
+
end.to_h.each_with_object({}) { |(k, v), h| h[k.downcase] = v }
|
285
|
+
|
286
|
+
copy = options[:request_body].dup
|
287
|
+
|
288
|
+
options[:request_body] = if text_based?(headers['content-type']) && options[:mask_body]
|
289
|
+
begin
|
290
|
+
parse_body(copy, options[:mask_body], headers['content-encoding'], headers['content-type'])
|
291
|
+
rescue BodyParsingError => e
|
292
|
+
log(e.message)
|
293
|
+
end
|
294
|
+
else
|
295
|
+
masked(copy).to_s
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def masked_data msg
|
300
|
+
case msg
|
301
|
+
when Hash
|
302
|
+
Hash[msg.map { |k, v| [k, config.filter_parameters.include?(k.downcase) ? PARAM_MASK : masked_data(v)] }]
|
303
|
+
when Array
|
304
|
+
msg.map { |element| masked_data(element) }
|
305
|
+
else
|
306
|
+
msg
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
239
310
|
def string_classes
|
240
311
|
@string_classes ||= begin
|
241
312
|
string_classes = [String]
|
data/lib/httplog/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: httplog
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thilo Rusche
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-02-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ethon
|
@@ -192,6 +192,20 @@ dependencies:
|
|
192
192
|
- - "~>"
|
193
193
|
- !ruby/object:Gem::Version
|
194
194
|
version: '1.7'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: oj
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: 3.9.2
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: 3.9.2
|
195
209
|
- !ruby/object:Gem::Dependency
|
196
210
|
name: rack
|
197
211
|
requirement: !ruby/object:Gem::Requirement
|
@@ -260,7 +274,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
260
274
|
- !ruby/object:Gem::Version
|
261
275
|
version: '0'
|
262
276
|
requirements: []
|
263
|
-
rubygems_version: 3.
|
277
|
+
rubygems_version: 3.1.2
|
264
278
|
signing_key:
|
265
279
|
specification_version: 4
|
266
280
|
summary: Log outgoing HTTP requests.
|