httplog 1.3.3 → 1.4.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/httplog.svg)](http://badge.fury.io/rb/httplog) [![Build Status](https://travis-ci.org/trusche/httplog.svg?branch=master)](https://travis-ci.org/trusche/httplog) [![Code Climate](https://codeclimate.com/github/trusche/httplog.svg)](https://codeclimate.com/github/trusche/httplog)
|
4
6
|
[![Release Version](https://img.shields.io/github/release/trusche/httplog.svg)](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.
|