httplog 1.3.0 → 1.4.3
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 +24 -0
- data/README.md +56 -19
- data/httplog.gemspec +4 -2
- 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 +28 -20
- data/lib/httplog/http_log.rb +130 -45
- data/lib/httplog/version.rb +1 -1
- metadata +18 -46
- 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: 9ce910d7c04847c28f836de26906c0c50aa4642b553b298d15067bf6ee25db56
|
|
4
|
+
data.tar.gz: 8600e756f28e033b6e39ac91235f69bf772fe467b05040d88c02ce54e982ef0b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6a76055079c7bfa4a229fda0861f90276c94cb64859ae7ce3c269e86656f3ad69fb3d5e8ed7eb2b82e9fe1facb0a73619951f099aafbbac7b0b503a52eb21046
|
|
7
|
+
data.tar.gz: f541eec119d175cc50563bc2673d60dae6246a43bcf570f9fe4db9a6295697ea94f7ffddb709aa8c9d0264f274f48c42bca3322d3364c1edd47298a984421363
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
## 1.4.3 - 2020-06-10
|
|
2
|
+
|
|
3
|
+
* Masking `password` parameter by default... doh.
|
|
4
|
+
|
|
5
|
+
## 1.4.2 - 2020-02-09
|
|
6
|
+
|
|
7
|
+
* Rollback of the previous two releases due to bugs introduced there.
|
|
8
|
+
|
|
9
|
+
## 1.4.1 - 2020-02-08 - YANKED
|
|
10
|
+
|
|
11
|
+
* [#91](https://github.com/trusche/httplog/pull/91) Fixed bug returning empty response with HTTP gem
|
|
12
|
+
|
|
13
|
+
## 1.4.0 - 2020-01-19 - YANKED
|
|
14
|
+
|
|
15
|
+
* [#85](https://github.com/trusche/httplog/pull/85) Parse JSON response and apply deep masking
|
|
16
|
+
|
|
17
|
+
## 1.3.3 - 2019-11-14
|
|
18
|
+
|
|
19
|
+
* [#83](https://github.com/trusche/httplog/pull/83) Support for graylog
|
|
20
|
+
|
|
21
|
+
## 1.3.1 - 2019-06-07
|
|
22
|
+
|
|
23
|
+
* [#76](https://github.com/trusche/httplog/pull/76) Added configurable logger method
|
|
24
|
+
|
|
1
25
|
## 1.3.0 - 2019-05-18
|
|
2
26
|
|
|
3
27
|
* [#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
|
@@ -1,11 +1,14 @@
|
|
|
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 or higher. 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)
|
|
7
|
+
<a href="https://www.bearer.sh?ref=httplog"><img src="/bearer-badge.png" height="20px"/></a>
|
|
5
8
|
|
|
6
9
|
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
10
|
|
|
8
|
-
Requires ruby >= 2.
|
|
11
|
+
Requires ruby >= 2.5
|
|
9
12
|
|
|
10
13
|
This gem works with the following ruby modules and libraries:
|
|
11
14
|
|
|
@@ -29,6 +32,10 @@ This is very much a development and debugging tool; it is **not recommended** to
|
|
|
29
32
|
use this in a production environment as it is monkey-patching the respective HTTP implementations.
|
|
30
33
|
You have been warned - use at your own risk.
|
|
31
34
|
|
|
35
|
+
Httplog is kindly sponsored by <a href="https://www.bearer.sh?ref=httplog">Bearer.sh</a> - go check them out please!
|
|
36
|
+
|
|
37
|
+
<a href="https://www.bearer.sh?ref=httplog"><img src="/bearer-sponsor.png" height="72px" /></a>
|
|
38
|
+
|
|
32
39
|
### Installation
|
|
33
40
|
|
|
34
41
|
gem install httplog
|
|
@@ -54,8 +61,9 @@ HttpLog.configure do |config|
|
|
|
54
61
|
# Enable or disable all logging
|
|
55
62
|
config.enabled = true
|
|
56
63
|
|
|
57
|
-
# You can assign a different logger
|
|
64
|
+
# You can assign a different logger or method to call on that logger
|
|
58
65
|
config.logger = Logger.new($stdout)
|
|
66
|
+
config.logger_method = :log
|
|
59
67
|
|
|
60
68
|
# I really wouldn't change this...
|
|
61
69
|
config.severity = Logger::Severity::DEBUG
|
|
@@ -82,7 +90,18 @@ HttpLog.configure do |config|
|
|
|
82
90
|
config.url_whitelist_pattern = nil
|
|
83
91
|
config.url_blacklist_pattern = nil
|
|
84
92
|
|
|
85
|
-
# Mask
|
|
93
|
+
# Mask sensitive information in request and response JSON data.
|
|
94
|
+
# Enable global JSON masking by setting the parameter to `/.*/`
|
|
95
|
+
config.url_masked_body_pattern = nil
|
|
96
|
+
|
|
97
|
+
# You can specify any custom JSON serializer that implements `load` and `dump` class methods
|
|
98
|
+
# to parse JSON responses
|
|
99
|
+
config.json_parser = JSON
|
|
100
|
+
|
|
101
|
+
# When using graylog, you can supply a formatter here - see below for details
|
|
102
|
+
config.graylog_formatter = nil
|
|
103
|
+
|
|
104
|
+
# Mask the values of sensitive request parameters
|
|
86
105
|
config.filter_parameters = %w[password]
|
|
87
106
|
end
|
|
88
107
|
```
|
|
@@ -97,6 +116,9 @@ HttpLog.configure do |config|
|
|
|
97
116
|
end
|
|
98
117
|
```
|
|
99
118
|
|
|
119
|
+
If you're running a (hopefully patched) legacy Rails 3 app, you may need to set
|
|
120
|
+
`config.logger_method = :add` due to its somewhat unusual logger.
|
|
121
|
+
|
|
100
122
|
You can colorize the output to make it stand out in your logfile, either with a single color
|
|
101
123
|
for the text:
|
|
102
124
|
|
|
@@ -116,6 +138,37 @@ end
|
|
|
116
138
|
|
|
117
139
|
For more color options please refer to the [rainbow documentation](https://github.com/sickill/rainbow)
|
|
118
140
|
|
|
141
|
+
### Graylog logging
|
|
142
|
+
|
|
143
|
+
If you use Graylog and want to use its search features such as "benchmark:>1 AND method:PUT",
|
|
144
|
+
you can use this configuration:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
FORMATTER = Lograge::Formatters::KeyValue.new
|
|
148
|
+
|
|
149
|
+
HttpLog.configure do |config|
|
|
150
|
+
config.logger = <your GELF::Logger>
|
|
151
|
+
config.logger_method = :add
|
|
152
|
+
config.severity = GELF::Levels::DEBUG
|
|
153
|
+
config.graylog_formatter = FORMATTER
|
|
154
|
+
end
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
You also can use GELF Graylog format this way:
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
class Lograge::Formatters::Graylog2HttpLog < Lograge::Formatters::Graylog2
|
|
161
|
+
def short_message data
|
|
162
|
+
data[:response_body] = data[:response_body].to_s.byteslice(0, 32_766) unless data[:response_body].blank?
|
|
163
|
+
"[httplog] [#{data[:response_code]}] #{data[:method]} #{data[:url]}"
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
FORMATTER = Lograge::Formatters::Graylog2HttpLog.new
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Or define your own class that implements the `call` method
|
|
171
|
+
|
|
119
172
|
### Compact logging
|
|
120
173
|
|
|
121
174
|
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.
|
|
@@ -228,19 +281,3 @@ This will launch a simple rack server on port 9292 and run all tests locally aga
|
|
|
228
281
|
If you have any issues with or feature requests for httplog,
|
|
229
282
|
please [open an issue](https://github.com/trusche/httplog/issues) on GitHub
|
|
230
283
|
or fork the project and send a pull request. **Please include passing specs with all pull requests.**
|
|
231
|
-
|
|
232
|
-
### Contributors
|
|
233
|
-
|
|
234
|
-
Thanks to these fine folks for contributing pull requests:
|
|
235
|
-
|
|
236
|
-
* [Doug Johnston](https://github.com/dougjohnston)
|
|
237
|
-
* [Eric Cohen](https://github.com/eirc)
|
|
238
|
-
* [Nikos Dimitrakopoulos](https://github.com/nikosd)
|
|
239
|
-
* [Marcos Hack](https://github.com/marcoshack)
|
|
240
|
-
* [Andrew Hammond](https://github.com/andrhamm)
|
|
241
|
-
* [Chris Keele](https://github.com/christhekeele)
|
|
242
|
-
* [Ryan Souza](https://github.com/ryansouza)
|
|
243
|
-
* [Ilya Bondarenko](https://github.com/sedx)
|
|
244
|
-
* [Kostas Zacharakis](https://github.com/kzacharakis)
|
|
245
|
-
* [Yuri Smirnov](https://github.com/tycooon)
|
|
246
|
-
* [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
|
|
|
@@ -27,7 +28,7 @@ Gem::Specification.new do |gem|
|
|
|
27
28
|
gem.add_development_dependency 'excon', ['~> 0.60']
|
|
28
29
|
gem.add_development_dependency 'faraday', ['~> 0.14']
|
|
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']
|
|
33
34
|
gem.add_development_dependency 'listen', ['~> 3.0']
|
|
@@ -36,6 +37,7 @@ Gem::Specification.new do |gem|
|
|
|
36
37
|
gem.add_development_dependency 'rspec', ['~> 3.7']
|
|
37
38
|
gem.add_development_dependency 'simplecov', ['~> 0.15']
|
|
38
39
|
gem.add_development_dependency 'thin', ['~> 1.7']
|
|
40
|
+
gem.add_development_dependency 'oj', ['>= 3.9.2']
|
|
39
41
|
|
|
40
42
|
gem.add_dependency 'rack', ['>= 1.0']
|
|
41
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
|
|
|
@@ -5,7 +5,9 @@ module HttpLog
|
|
|
5
5
|
attr_accessor :enabled,
|
|
6
6
|
:compact_log,
|
|
7
7
|
:json_log,
|
|
8
|
+
:graylog_formatter,
|
|
8
9
|
:logger,
|
|
10
|
+
:logger_method,
|
|
9
11
|
:severity,
|
|
10
12
|
:prefix,
|
|
11
13
|
:log_connect,
|
|
@@ -17,33 +19,39 @@ module HttpLog
|
|
|
17
19
|
:log_benchmark,
|
|
18
20
|
:url_whitelist_pattern,
|
|
19
21
|
:url_blacklist_pattern,
|
|
22
|
+
:url_masked_body_pattern,
|
|
20
23
|
:color,
|
|
21
24
|
:prefix_data_lines,
|
|
22
25
|
:prefix_response_lines,
|
|
23
26
|
:prefix_line_numbers,
|
|
27
|
+
:json_parser,
|
|
24
28
|
:filter_parameters
|
|
25
29
|
|
|
26
30
|
def initialize
|
|
27
|
-
@enabled
|
|
28
|
-
@compact_log
|
|
29
|
-
@json_log
|
|
30
|
-
@
|
|
31
|
-
@
|
|
32
|
-
@
|
|
33
|
-
@
|
|
34
|
-
@
|
|
35
|
-
@
|
|
36
|
-
@
|
|
37
|
-
@
|
|
38
|
-
@
|
|
39
|
-
@
|
|
40
|
-
@
|
|
41
|
-
@
|
|
42
|
-
@
|
|
43
|
-
@
|
|
44
|
-
@
|
|
45
|
-
@
|
|
46
|
-
@
|
|
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]
|
|
47
55
|
end
|
|
48
56
|
end
|
|
49
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.
|
|
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,17 +111,19 @@ 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
|
|
|
124
|
+
body = body.to_s if defined?(HTTP::Response::Body) && body.is_a?(HTTP::Response::Body)
|
|
125
|
+
body = body.dup
|
|
126
|
+
|
|
118
127
|
if encoding =~ /gzip/ && body && !body.empty?
|
|
119
128
|
begin
|
|
120
129
|
sio = StringIO.new(body.to_s)
|
|
@@ -125,14 +134,25 @@ module HttpLog
|
|
|
125
134
|
end
|
|
126
135
|
end
|
|
127
136
|
|
|
128
|
-
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
|
|
129
151
|
end
|
|
130
152
|
|
|
131
153
|
def log_data(data)
|
|
132
154
|
return unless config.log_data
|
|
133
155
|
|
|
134
|
-
data = utf_encoded(masked(data.dup).to_s) unless data.nil?
|
|
135
|
-
|
|
136
156
|
if config.prefix_data_lines
|
|
137
157
|
log('Data:')
|
|
138
158
|
log_data_lines(data)
|
|
@@ -147,38 +167,6 @@ module HttpLog
|
|
|
147
167
|
log("#{method.to_s.upcase} #{masked(uri)} completed with status code #{status} in #{seconds.to_f.round(6)} seconds")
|
|
148
168
|
end
|
|
149
169
|
|
|
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
170
|
def transform_response_code(response_code_name)
|
|
183
171
|
Rack::Utils::HTTP_STATUS_CODES.detect { |_k, v| v.to_s.casecmp(response_code_name.to_s).zero? }.first
|
|
184
172
|
end
|
|
@@ -199,6 +187,70 @@ module HttpLog
|
|
|
199
187
|
|
|
200
188
|
private
|
|
201
189
|
|
|
190
|
+
def log_json(data = {})
|
|
191
|
+
return unless config.json_log
|
|
192
|
+
|
|
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))
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def log_graylog(data)
|
|
209
|
+
result = json_payload(data)
|
|
210
|
+
begin
|
|
211
|
+
send_to_graylog result
|
|
212
|
+
rescue
|
|
213
|
+
result[:response_body] = 'Graylog JSON dump failed'
|
|
214
|
+
result[:request_body] = 'Graylog JSON dump failed'
|
|
215
|
+
send_to_graylog result
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def send_to_graylog data
|
|
220
|
+
data.compact!
|
|
221
|
+
config.logger.public_send(config.logger_method, config.severity, config.graylog_formatter.call(data))
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def json_payload(data = {})
|
|
225
|
+
data[:response_code] = transform_response_code(data[:response_code]) if data[:response_code].is_a?(Symbol)
|
|
226
|
+
|
|
227
|
+
parsed_body = begin
|
|
228
|
+
parse_body(data[:response_body], data[:mask_body], data[:encoding], data[:content_type])
|
|
229
|
+
rescue BodyParsingError => e
|
|
230
|
+
e.message
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
if config.compact_log
|
|
234
|
+
{
|
|
235
|
+
method: data[:method].to_s.upcase,
|
|
236
|
+
url: masked(data[:url]),
|
|
237
|
+
response_code: data[:response_code].to_i,
|
|
238
|
+
benchmark: data[:benchmark]
|
|
239
|
+
}
|
|
240
|
+
else
|
|
241
|
+
{
|
|
242
|
+
method: data[:method].to_s.upcase,
|
|
243
|
+
url: masked(data[:url]),
|
|
244
|
+
request_body: data[:request_body],
|
|
245
|
+
request_headers: masked(data[:request_headers].to_h),
|
|
246
|
+
response_code: data[:response_code].to_i,
|
|
247
|
+
response_body: parsed_body,
|
|
248
|
+
response_headers: data[:response_headers].to_h,
|
|
249
|
+
benchmark: data[:benchmark]
|
|
250
|
+
}
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
202
254
|
def masked(msg, key=nil)
|
|
203
255
|
return msg if config.filter_parameters.empty?
|
|
204
256
|
return msg if msg.nil?
|
|
@@ -211,7 +263,8 @@ module HttpLog
|
|
|
211
263
|
case msg
|
|
212
264
|
when *string_classes
|
|
213
265
|
config.filter_parameters.reduce(msg) do |m,key|
|
|
214
|
-
m.to_s.
|
|
266
|
+
scrubbed = m.to_s.encode('UTF-8', invalid: :replace, undef: :replace)
|
|
267
|
+
scrubbed.gsub(/(#{key})=[^&]+/i, "#{key}=#{PARAM_MASK}")
|
|
215
268
|
end
|
|
216
269
|
# ...and recurse over hashes
|
|
217
270
|
when *hash_classes
|
|
@@ -222,6 +275,38 @@ module HttpLog
|
|
|
222
275
|
end
|
|
223
276
|
end
|
|
224
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
|
+
|
|
225
310
|
def string_classes
|
|
226
311
|
@string_classes ||= begin
|
|
227
312
|
string_classes = [String]
|