httplog 1.3.1 → 1.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 +32 -0
- data/README.md +53 -22
- data/httplog.gemspec +7 -4
- 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 +13 -2
- data/lib/httplog/adapters/patron.rb +2 -1
- data/lib/httplog/configuration.rb +27 -21
- data/lib/httplog/http_log.rb +138 -49
- data/lib/httplog/version.rb +1 -1
- metadata +37 -51
- data/.gitignore +0 -7
- data/.rspec +0 -2
- data/.rubocop.yml +0 -55
- data/.rubocop_todo.yml +0 -36
- data/.ruby-version +0 -1
- data/.travis.yml +0 -17
- data/Gemfile +0 -4
- data/Gemfile.lock +0 -130
- data/Guardfile +0 -25
- data/MIT-LICENSE +0 -20
- data/Rakefile +0 -46
- data/gemfiles/http3.gemfile +0 -7
- data/gemfiles/http4.gemfile +0 -7
- data/gemfiles/http5.gemfile +0 -7
- data/gemfiles/rack1.gemfile +0 -7
- data/gemfiles/rack2.gemfile +0 -7
- data/spec/adapters/ethon_adapter.rb +0 -26
- data/spec/adapters/excon_adapter.rb +0 -16
- data/spec/adapters/faraday_adapter.rb +0 -59
- data/spec/adapters/http_adapter.rb +0 -27
- data/spec/adapters/http_base_adapter.rb +0 -39
- data/spec/adapters/httparty_adapter.rb +0 -16
- data/spec/adapters/httpclient_adapter.rb +0 -31
- data/spec/adapters/net_http_adapter.rb +0 -21
- data/spec/adapters/open_uri_adapter.rb +0 -19
- data/spec/adapters/patron_adapter.rb +0 -36
- data/spec/adapters/typhoeus_adapter.rb +0 -28
- data/spec/configuration_spec.rb +0 -22
- data/spec/lib/http_client_spec.rb +0 -15
- data/spec/lib/http_log_spec.rb +0 -320
- data/spec/log/.gitkeep +0 -0
- data/spec/spec_helper.rb +0 -45
- data/spec/support/index.html +0 -8
- data/spec/support/index.html.gz +0 -0
- data/spec/support/log4r.yml +0 -17
- data/spec/support/not_gzipped.html.gz +0 -8
- data/spec/support/shared_examples.rb +0 -79
- data/spec/support/test.bin +0 -0
- data/spec/support/test.pdf +0 -198
- data/spec/support/test_server.rb +0 -34
- data/spec/support/utf8-invalid.html +0 -0
- data/spec/support/utf8.html +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97cbba2cdb0af09dcf6664e290981b074a40f9c91d81be74a737a746bfc1e612
|
4
|
+
data.tar.gz: 228590a39ab68d89d3261f219e2bac4945f4fde315219943cbbacdfc1aaa2922
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d857f2dc7142fb7243371afd4d3993b65ced6092ecb6ac361abbd2957bc1884e18131ca707b3e2c9dc694ad9996c09e12ddf7b31f978d8d55def35322159a861
|
7
|
+
data.tar.gz: eafbb1ef94500b468083f8073049749e6073d2ec423fadae8f6aca3221624bb914f28a489305f35cd5fb222b247eb81f5b2446f4694f919959267d83c0bfcbe7
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,35 @@
|
|
1
|
+
## 1.5.0 - 2021-05-20
|
2
|
+
|
3
|
+
* Support for Ruby 2.7 and frozen strings
|
4
|
+
* Development dependency updates
|
5
|
+
* Dropped support for net/http v3
|
6
|
+
* Performance tweaks
|
7
|
+
* Fix for RestClient body read issue (WARNING: this may be reverted, see [#105](https://github.com/trusche/httplog/issues/105))
|
8
|
+
|
9
|
+
## 1.4.3 - 2020-06-10
|
10
|
+
|
11
|
+
* Masking `password` parameter by default... doh.
|
12
|
+
|
13
|
+
## 1.4.2 - 2020-02-09
|
14
|
+
|
15
|
+
* Rollback of the previous two releases due to bugs introduced there.
|
16
|
+
|
17
|
+
## 1.4.1 - 2020-02-08 - YANKED
|
18
|
+
|
19
|
+
* [#91](https://github.com/trusche/httplog/pull/91) Fixed bug returning empty response with HTTP gem
|
20
|
+
|
21
|
+
## 1.4.0 - 2020-01-19 - YANKED
|
22
|
+
|
23
|
+
* [#85](https://github.com/trusche/httplog/pull/85) Parse JSON response and apply deep masking
|
24
|
+
|
25
|
+
## 1.3.3 - 2019-11-14
|
26
|
+
|
27
|
+
* [#83](https://github.com/trusche/httplog/pull/83) Support for graylog
|
28
|
+
|
29
|
+
## 1.3.1 - 2019-06-07
|
30
|
+
|
31
|
+
* [#76](https://github.com/trusche/httplog/pull/76) Added configurable logger method
|
32
|
+
|
1
33
|
## 1.3.0 - 2019-05-18
|
2
34
|
|
3
35
|
* [#74](https://github.com/trusche/httplog/pull/74) Added ability to filter sensitive parameter values in the request (based on [#73](https://github.com/trusche/httplog/pull/73)). Default masking of `password` parameter
|
data/README.md
CHANGED
@@ -2,14 +2,15 @@
|
|
2
2
|
|
3
3
|
[](http://badge.fury.io/rb/httplog) [](https://travis-ci.org/trusche/httplog) [](https://codeclimate.com/github/trusche/httplog)
|
4
4
|
[](https://img.shields.io/github/release/trusche/httplog.svg)
|
5
|
+
<a href="https://www.bearer.sh?ref=httplog"><img src="/bearer-badge.png" height="20px"/></a>
|
5
6
|
|
6
7
|
Log outgoing HTTP requests made from your application. Helps with debugging pesky API error responses, or just generally understanding what's going on under the hood.
|
7
8
|
|
8
|
-
Requires ruby >= 2.
|
9
|
+
Requires ruby >= 2.5
|
9
10
|
|
10
11
|
This gem works with the following ruby modules and libraries:
|
11
12
|
|
12
|
-
* [Net::HTTP](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/net/http/rdoc/index.html)
|
13
|
+
* [Net::HTTP](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/net/http/rdoc/index.html) v4+
|
13
14
|
* [Ethon](https://github.com/typhoeus/ethon)
|
14
15
|
* [Excon](https://github.com/geemus/excon)
|
15
16
|
* [OpenURI](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/open-uri/rdoc/index.html)
|
@@ -29,6 +30,10 @@ This is very much a development and debugging tool; it is **not recommended** to
|
|
29
30
|
use this in a production environment as it is monkey-patching the respective HTTP implementations.
|
30
31
|
You have been warned - use at your own risk.
|
31
32
|
|
33
|
+
Httplog is kindly sponsored by <a href="https://www.bearer.sh?ref=httplog">Bearer.sh</a> - go check them out please!
|
34
|
+
|
35
|
+
<a href="https://www.bearer.sh?ref=httplog"><img src="/bearer-sponsor.png" height="72px" /></a>
|
36
|
+
|
32
37
|
### Installation
|
33
38
|
|
34
39
|
gem install httplog
|
@@ -83,8 +88,22 @@ HttpLog.configure do |config|
|
|
83
88
|
config.url_whitelist_pattern = nil
|
84
89
|
config.url_blacklist_pattern = nil
|
85
90
|
|
86
|
-
# 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
|
+
# When using graylog, you can supply a formatter here - see below for details
|
100
|
+
config.graylog_formatter = nil
|
101
|
+
|
102
|
+
# Mask the values of sensitive request parameters
|
87
103
|
config.filter_parameters = %w[password]
|
104
|
+
|
105
|
+
# Customize the prefix with a proc or lambda
|
106
|
+
config.prefix = ->{ "[httplog] #{Time.now} " }
|
88
107
|
end
|
89
108
|
```
|
90
109
|
|
@@ -120,6 +139,37 @@ end
|
|
120
139
|
|
121
140
|
For more color options please refer to the [rainbow documentation](https://github.com/sickill/rainbow)
|
122
141
|
|
142
|
+
### Graylog logging
|
143
|
+
|
144
|
+
If you use Graylog and want to use its search features such as "benchmark:>1 AND method:PUT",
|
145
|
+
you can use this configuration:
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
FORMATTER = Lograge::Formatters::KeyValue.new
|
149
|
+
|
150
|
+
HttpLog.configure do |config|
|
151
|
+
config.logger = <your GELF::Logger>
|
152
|
+
config.logger_method = :add
|
153
|
+
config.severity = GELF::Levels::DEBUG
|
154
|
+
config.graylog_formatter = FORMATTER
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
You also can use GELF Graylog format this way:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
class Lograge::Formatters::Graylog2HttpLog < Lograge::Formatters::Graylog2
|
162
|
+
def short_message data
|
163
|
+
data[:response_body] = data[:response_body].to_s.byteslice(0, 32_766) unless data[:response_body].blank?
|
164
|
+
"[httplog] [#{data[:response_code]}] #{data[:method]} #{data[:url]}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
FORMATTER = Lograge::Formatters::Graylog2HttpLog.new
|
169
|
+
```
|
170
|
+
|
171
|
+
Or define your own class that implements the `call` method
|
172
|
+
|
123
173
|
### Compact logging
|
124
174
|
|
125
175
|
If the log is too noisy for you, but you don't want to completely disable it either, set the `compact_log` option to `true`. This will log each request in a single line with method, request URI, response status and time, but no data or headers. No need to disable any other options individually.
|
@@ -218,9 +268,6 @@ a suggestion for a fix, please open an issue or, even better, submit a pull requ
|
|
218
268
|
|
219
269
|
* Benchmarking only covers the time between starting the HTTP request and receiving the response. It does *not* cover the time it takes to establish the TCP connection.
|
220
270
|
|
221
|
-
* When using [REST Client](https://github.com/rest-client/rest-client), POST requests might be missing the requests
|
222
|
-
data. See #54 for details.
|
223
|
-
|
224
271
|
### Running the specs
|
225
272
|
|
226
273
|
Make sure you have the necessary dependencies installed by running `bundle install`.
|
@@ -232,19 +279,3 @@ This will launch a simple rack server on port 9292 and run all tests locally aga
|
|
232
279
|
If you have any issues with or feature requests for httplog,
|
233
280
|
please [open an issue](https://github.com/trusche/httplog/issues) on GitHub
|
234
281
|
or fork the project and send a pull request. **Please include passing specs with all pull requests.**
|
235
|
-
|
236
|
-
### Contributors
|
237
|
-
|
238
|
-
Thanks to these fine folks for contributing pull requests:
|
239
|
-
|
240
|
-
* [Doug Johnston](https://github.com/dougjohnston)
|
241
|
-
* [Eric Cohen](https://github.com/eirc)
|
242
|
-
* [Nikos Dimitrakopoulos](https://github.com/nikosd)
|
243
|
-
* [Marcos Hack](https://github.com/marcoshack)
|
244
|
-
* [Andrew Hammond](https://github.com/andrhamm)
|
245
|
-
* [Chris Keele](https://github.com/christhekeele)
|
246
|
-
* [Ryan Souza](https://github.com/ryansouza)
|
247
|
-
* [Ilya Bondarenko](https://github.com/sedx)
|
248
|
-
* [Kostas Zacharakis](https://github.com/kzacharakis)
|
249
|
-
* [Yuri Smirnov](https://github.com/tycooon)
|
250
|
-
* [Manuel Bustillo Alonso](https://github.com/bustikiller)
|
data/httplog.gemspec
CHANGED
@@ -17,7 +17,8 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.description = "Log outgoing HTTP requests made from your application. Helpful for tracking API calls
|
18
18
|
of third party gems that don't provide their own log output."
|
19
19
|
|
20
|
-
gem.files =
|
20
|
+
gem.files = Dir['lib/**/*.rb'] +
|
21
|
+
%w(httplog.gemspec README.md CHANGELOG.md)
|
21
22
|
gem.test_files = `git ls-files -- test/*`.split("\n")
|
22
23
|
gem.require_paths = ['lib']
|
23
24
|
|
@@ -25,17 +26,19 @@ Gem::Specification.new do |gem|
|
|
25
26
|
|
26
27
|
gem.add_development_dependency 'ethon', ['~> 0.11']
|
27
28
|
gem.add_development_dependency 'excon', ['~> 0.60']
|
28
|
-
gem.add_development_dependency 'faraday', ['~>
|
29
|
+
gem.add_development_dependency 'faraday', ['~> 1.3']
|
29
30
|
gem.add_development_dependency 'guard-rspec', ['~> 4.7']
|
30
|
-
gem.add_development_dependency 'http', ['~>
|
31
|
+
gem.add_development_dependency 'http', ['~> 4.0']
|
31
32
|
gem.add_development_dependency 'httparty', ['~> 0.16']
|
32
33
|
gem.add_development_dependency 'httpclient', ['~> 2.8']
|
34
|
+
gem.add_development_dependency 'rest-client', ['~> 2.0']
|
33
35
|
gem.add_development_dependency 'listen', ['~> 3.0']
|
34
36
|
gem.add_development_dependency 'patron', ['~> 0.12']
|
35
|
-
gem.add_development_dependency 'rake', ['~>
|
37
|
+
gem.add_development_dependency 'rake', ['~> 13.0']
|
36
38
|
gem.add_development_dependency 'rspec', ['~> 3.7']
|
37
39
|
gem.add_development_dependency 'simplecov', ['~> 0.15']
|
38
40
|
gem.add_development_dependency 'thin', ['~> 1.7']
|
41
|
+
gem.add_development_dependency 'oj', ['>= 3.9.2']
|
39
42
|
|
40
43
|
gem.add_dependency 'rack', ['>= 1.0']
|
41
44
|
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}")
|
@@ -11,19 +11,30 @@ module Net
|
|
11
11
|
bm = Benchmark.realtime do
|
12
12
|
@response = orig_request(req, body, &block)
|
13
13
|
end
|
14
|
+
body_stream = req.body_stream
|
15
|
+
request_body = if body_stream
|
16
|
+
body_stream.to_s # read and rewind for RestClient::Payload::Base
|
17
|
+
body_stream.rewind if body_stream.respond_to?(:rewind) # RestClient::Payload::Base has no method rewind
|
18
|
+
body_stream.read
|
19
|
+
elsif req.body.nil? || req.body.empty?
|
20
|
+
body
|
21
|
+
else
|
22
|
+
req.body
|
23
|
+
end
|
14
24
|
|
15
25
|
if HttpLog.url_approved?(url) && started?
|
16
26
|
HttpLog.call(
|
17
27
|
method: req.method,
|
18
28
|
url: url,
|
19
|
-
request_body:
|
29
|
+
request_body: request_body,
|
20
30
|
request_headers: req.each_header.collect,
|
21
31
|
response_code: @response.code,
|
22
32
|
response_body: @response.body,
|
23
33
|
response_headers: @response.each_header.collect,
|
24
34
|
benchmark: bm,
|
25
35
|
encoding: @response['Content-Encoding'],
|
26
|
-
content_type: @response['Content-Type']
|
36
|
+
content_type: @response['Content-Type'],
|
37
|
+
mask_body: HttpLog.masked_body_url?(url)
|
27
38
|
)
|
28
39
|
end
|
29
40
|
|
@@ -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
|
|
@@ -5,6 +5,7 @@ module HttpLog
|
|
5
5
|
attr_accessor :enabled,
|
6
6
|
:compact_log,
|
7
7
|
:json_log,
|
8
|
+
:graylog_formatter,
|
8
9
|
:logger,
|
9
10
|
:logger_method,
|
10
11
|
:severity,
|
@@ -18,34 +19,39 @@ module HttpLog
|
|
18
19
|
:log_benchmark,
|
19
20
|
:url_whitelist_pattern,
|
20
21
|
:url_blacklist_pattern,
|
22
|
+
:url_masked_body_pattern,
|
21
23
|
:color,
|
22
24
|
:prefix_data_lines,
|
23
25
|
:prefix_response_lines,
|
24
26
|
:prefix_line_numbers,
|
27
|
+
:json_parser,
|
25
28
|
:filter_parameters
|
26
29
|
|
27
30
|
def initialize
|
28
|
-
@enabled
|
29
|
-
@compact_log
|
30
|
-
@json_log
|
31
|
-
@
|
32
|
-
@
|
33
|
-
@
|
34
|
-
@
|
35
|
-
@
|
36
|
-
@
|
37
|
-
@
|
38
|
-
@
|
39
|
-
@
|
40
|
-
@
|
41
|
-
@
|
42
|
-
@
|
43
|
-
@
|
44
|
-
@
|
45
|
-
@
|
46
|
-
@
|
47
|
-
@
|
48
|
-
@
|
31
|
+
@enabled = true
|
32
|
+
@compact_log = false
|
33
|
+
@json_log = false
|
34
|
+
@graylog_formatter = nil
|
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 = %w[password]
|
49
55
|
end
|
50
56
|
end
|
51
57
|
end
|
data/lib/httplog/http_log.rb
CHANGED
@@ -29,8 +29,11 @@ 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)
|
35
|
+
elsif config.graylog_formatter
|
36
|
+
log_graylog(options)
|
34
37
|
elsif config.compact_log
|
35
38
|
log_compact(options[:method], options[:url], options[:response_code], options[:benchmark])
|
36
39
|
else
|
@@ -40,7 +43,7 @@ module HttpLog
|
|
40
43
|
HttpLog.log_status(options[:response_code])
|
41
44
|
HttpLog.log_benchmark(options[:benchmark])
|
42
45
|
HttpLog.log_headers(options[:response_headers])
|
43
|
-
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])
|
44
47
|
end
|
45
48
|
end
|
46
49
|
|
@@ -50,10 +53,14 @@ module HttpLog
|
|
50
53
|
!config.url_whitelist_pattern || url.to_s.match(config.url_whitelist_pattern)
|
51
54
|
end
|
52
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
|
+
|
53
60
|
def log(msg)
|
54
61
|
return unless config.enabled
|
55
62
|
|
56
|
-
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))
|
57
64
|
end
|
58
65
|
|
59
66
|
def log_connection(host, port = nil)
|
@@ -89,10 +96,10 @@ module HttpLog
|
|
89
96
|
log("Benchmark: #{seconds.to_f.round(6)} seconds")
|
90
97
|
end
|
91
98
|
|
92
|
-
def log_body(body, encoding = nil, content_type = nil)
|
99
|
+
def log_body(body, mask_body, encoding = nil, content_type = nil)
|
93
100
|
return unless config.log_response
|
94
101
|
|
95
|
-
data = parse_body(body, encoding, content_type)
|
102
|
+
data = parse_body(body, mask_body, encoding, content_type)
|
96
103
|
|
97
104
|
if config.prefix_response_lines
|
98
105
|
log('Response:')
|
@@ -104,35 +111,50 @@ module HttpLog
|
|
104
111
|
log("Response: #{e.message}")
|
105
112
|
end
|
106
113
|
|
107
|
-
def parse_body(body, encoding, content_type)
|
108
|
-
unless text_based?(content_type)
|
109
|
-
|
110
|
-
end
|
114
|
+
def parse_body(body, mask_body, encoding, content_type)
|
115
|
+
raise BodyParsingError, "(not showing binary data)" unless text_based?(content_type)
|
116
|
+
|
111
117
|
|
112
118
|
if body.is_a?(Net::ReadAdapter)
|
113
119
|
# open-uri wraps the response in a Net::ReadAdapter that defers reading
|
114
|
-
# the content, so the
|
120
|
+
# the content, so the response body is not available here.
|
115
121
|
raise BodyParsingError, '(not available yet)'
|
116
122
|
end
|
117
123
|
|
118
|
-
|
124
|
+
body_copy = body.dup
|
125
|
+
body_copy = body.to_s if defined?(HTTP::Response::Body) && body.is_a?(HTTP::Response::Body)
|
126
|
+
return nil if body_copy.nil? || body_copy.empty?
|
127
|
+
|
128
|
+
|
129
|
+
if encoding =~ /gzip/
|
119
130
|
begin
|
120
|
-
sio = StringIO.new(
|
131
|
+
sio = StringIO.new(body_copy.to_s)
|
121
132
|
gz = Zlib::GzipReader.new(sio)
|
122
|
-
|
133
|
+
body_copy = gz.read
|
123
134
|
rescue Zlib::GzipFile::Error
|
124
135
|
log("(gzip decompression failed)")
|
125
136
|
end
|
126
137
|
end
|
127
138
|
|
128
|
-
utf_encoded(
|
139
|
+
result = utf_encoded(body_copy.to_s, content_type)
|
140
|
+
|
141
|
+
if mask_body
|
142
|
+
if content_type =~ /json/
|
143
|
+
result = begin
|
144
|
+
masked_data config.json_parser.load(result)
|
145
|
+
rescue => e
|
146
|
+
'Failed to mask response body: ' + e.message
|
147
|
+
end
|
148
|
+
else
|
149
|
+
result = masked(result)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
result
|
129
153
|
end
|
130
154
|
|
131
155
|
def log_data(data)
|
132
156
|
return unless config.log_data
|
133
157
|
|
134
|
-
data = utf_encoded(masked(data.dup).to_s) unless data.nil?
|
135
|
-
|
136
158
|
if config.prefix_data_lines
|
137
159
|
log('Data:')
|
138
160
|
log_data_lines(data)
|
@@ -147,38 +169,6 @@ module HttpLog
|
|
147
169
|
log("#{method.to_s.upcase} #{masked(uri)} completed with status code #{status} in #{seconds.to_f.round(6)} seconds")
|
148
170
|
end
|
149
171
|
|
150
|
-
def log_json(data = {})
|
151
|
-
return unless config.json_log
|
152
|
-
|
153
|
-
data[:response_code] = transform_response_code(data[:response_code]) if data[:response_code].is_a?(Symbol)
|
154
|
-
|
155
|
-
parsed_body = begin
|
156
|
-
parse_body(data[:response_body], data[:encoding], data[:content_type])
|
157
|
-
rescue BodyParsingError => e
|
158
|
-
e.message
|
159
|
-
end
|
160
|
-
|
161
|
-
if config.compact_log
|
162
|
-
log({
|
163
|
-
method: data[:method].to_s.upcase,
|
164
|
-
url: masked(data[:url]),
|
165
|
-
response_code: data[:response_code].to_i,
|
166
|
-
benchmark: data[:benchmark]
|
167
|
-
}.to_json)
|
168
|
-
else
|
169
|
-
log({
|
170
|
-
method: data[:method].to_s.upcase,
|
171
|
-
url: masked(data[:url]),
|
172
|
-
request_body: masked(data[:request_body]),
|
173
|
-
request_headers: masked(data[:request_headers].to_h),
|
174
|
-
response_code: data[:response_code].to_i,
|
175
|
-
response_body: parsed_body,
|
176
|
-
response_headers: data[:response_headers].to_h,
|
177
|
-
benchmark: data[:benchmark]
|
178
|
-
}.to_json)
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
172
|
def transform_response_code(response_code_name)
|
183
173
|
Rack::Utils::HTTP_STATUS_CODES.detect { |_k, v| v.to_s.casecmp(response_code_name.to_s).zero? }.first
|
184
174
|
end
|
@@ -199,6 +189,70 @@ module HttpLog
|
|
199
189
|
|
200
190
|
private
|
201
191
|
|
192
|
+
def log_json(data = {})
|
193
|
+
return unless config.json_log
|
194
|
+
|
195
|
+
log(
|
196
|
+
begin
|
197
|
+
dump_json(data)
|
198
|
+
rescue
|
199
|
+
data[:response_body] = "#{config.json_parser} dump failed"
|
200
|
+
data[:request_body] = "#{config.json_parser} dump failed"
|
201
|
+
dump_json(data)
|
202
|
+
end
|
203
|
+
)
|
204
|
+
end
|
205
|
+
|
206
|
+
def dump_json(data)
|
207
|
+
config.json_parser.dump(json_payload(data))
|
208
|
+
end
|
209
|
+
|
210
|
+
def log_graylog(data)
|
211
|
+
result = json_payload(data)
|
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
|
+
data.compact!
|
223
|
+
config.logger.public_send(config.logger_method, config.severity, config.graylog_formatter.call(data))
|
224
|
+
end
|
225
|
+
|
226
|
+
def json_payload(data = {})
|
227
|
+
data[:response_code] = transform_response_code(data[:response_code]) if data[:response_code].is_a?(Symbol)
|
228
|
+
|
229
|
+
parsed_body = begin
|
230
|
+
parse_body(data[:response_body], data[:mask_body], data[:encoding], data[:content_type])
|
231
|
+
rescue BodyParsingError => e
|
232
|
+
e.message
|
233
|
+
end
|
234
|
+
|
235
|
+
if config.compact_log
|
236
|
+
{
|
237
|
+
method: data[:method].to_s.upcase,
|
238
|
+
url: masked(data[:url]),
|
239
|
+
response_code: data[:response_code].to_i,
|
240
|
+
benchmark: data[:benchmark]
|
241
|
+
}
|
242
|
+
else
|
243
|
+
{
|
244
|
+
method: data[:method].to_s.upcase,
|
245
|
+
url: masked(data[:url]),
|
246
|
+
request_body: data[:request_body],
|
247
|
+
request_headers: masked(data[:request_headers].to_h),
|
248
|
+
response_code: data[:response_code].to_i,
|
249
|
+
response_body: parsed_body,
|
250
|
+
response_headers: data[:response_headers].to_h,
|
251
|
+
benchmark: data[:benchmark]
|
252
|
+
}
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
202
256
|
def masked(msg, key=nil)
|
203
257
|
return msg if config.filter_parameters.empty?
|
204
258
|
return msg if msg.nil?
|
@@ -207,11 +261,14 @@ module HttpLog
|
|
207
261
|
# in its entirety.
|
208
262
|
return (config.filter_parameters.include?(key.downcase) ? PARAM_MASK : msg) if key
|
209
263
|
|
210
|
-
# Otherwise, we'll parse Strings for key=
|
264
|
+
# Otherwise, we'll parse Strings for key=value pairs,
|
265
|
+
# for name="key"\n value...
|
211
266
|
case msg
|
212
267
|
when *string_classes
|
213
268
|
config.filter_parameters.reduce(msg) do |m,key|
|
214
|
-
m.to_s.
|
269
|
+
scrubbed = m.to_s.encode('UTF-8', invalid: :replace, undef: :replace)
|
270
|
+
scrubbed.to_s.gsub(/(#{key})=[^&]+/i, "#{key}=#{PARAM_MASK}")
|
271
|
+
.gsub(/name="#{key}"\s+\K[\s\w]+/, "#{PARAM_MASK}\r\n") # multi-part Faraday
|
215
272
|
end
|
216
273
|
# ...and recurse over hashes
|
217
274
|
when *hash_classes
|
@@ -222,6 +279,38 @@ module HttpLog
|
|
222
279
|
end
|
223
280
|
end
|
224
281
|
|
282
|
+
def parse_request(options)
|
283
|
+
return if options[:request_body].nil?
|
284
|
+
|
285
|
+
# Downcase content-type and content-encoding because ::HTTP returns "Content-Type" and "Content-Encoding"
|
286
|
+
headers = options[:request_headers].find_all do |header, _|
|
287
|
+
%w[content-type Content-Type content-encoding Content-Encoding].include? header
|
288
|
+
end.to_h.each_with_object({}) { |(k, v), h| h[k.downcase] = v }
|
289
|
+
|
290
|
+
copy = options[:request_body].dup
|
291
|
+
|
292
|
+
options[:request_body] = if text_based?(headers['content-type']) && options[:mask_body]
|
293
|
+
begin
|
294
|
+
parse_body(copy, options[:mask_body], headers['content-encoding'], headers['content-type'])
|
295
|
+
rescue BodyParsingError => e
|
296
|
+
log(e.message)
|
297
|
+
end
|
298
|
+
else
|
299
|
+
masked(copy).to_s
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
def masked_data msg
|
304
|
+
case msg
|
305
|
+
when Hash
|
306
|
+
Hash[msg.map { |k, v| [k, config.filter_parameters.include?(k.downcase) ? PARAM_MASK : masked_data(v)] }]
|
307
|
+
when Array
|
308
|
+
msg.map { |element| masked_data(element) }
|
309
|
+
else
|
310
|
+
msg
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
225
314
|
def string_classes
|
226
315
|
@string_classes ||= begin
|
227
316
|
string_classes = [String]
|