httplog 1.3.1 → 1.5.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 +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
|
[![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
4
|
[![Release Version](https://img.shields.io/github/release/trusche/httplog.svg)](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]
|