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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/README.md +56 -19
  4. data/httplog.gemspec +4 -2
  5. data/lib/httplog/adapters/ethon.rb +2 -1
  6. data/lib/httplog/adapters/excon.rb +2 -1
  7. data/lib/httplog/adapters/http.rb +5 -3
  8. data/lib/httplog/adapters/httpclient.rb +6 -4
  9. data/lib/httplog/adapters/net_http.rb +2 -1
  10. data/lib/httplog/adapters/patron.rb +2 -1
  11. data/lib/httplog/configuration.rb +28 -20
  12. data/lib/httplog/http_log.rb +130 -45
  13. data/lib/httplog/version.rb +1 -1
  14. metadata +18 -46
  15. data/.gitignore +0 -7
  16. data/.rspec +0 -2
  17. data/.rubocop.yml +0 -55
  18. data/.rubocop_todo.yml +0 -36
  19. data/.ruby-version +0 -1
  20. data/.travis.yml +0 -17
  21. data/Gemfile +0 -4
  22. data/Gemfile.lock +0 -130
  23. data/Guardfile +0 -25
  24. data/MIT-LICENSE +0 -20
  25. data/Rakefile +0 -46
  26. data/gemfiles/http3.gemfile +0 -7
  27. data/gemfiles/http4.gemfile +0 -7
  28. data/gemfiles/http5.gemfile +0 -7
  29. data/gemfiles/rack1.gemfile +0 -7
  30. data/gemfiles/rack2.gemfile +0 -7
  31. data/spec/adapters/ethon_adapter.rb +0 -26
  32. data/spec/adapters/excon_adapter.rb +0 -16
  33. data/spec/adapters/faraday_adapter.rb +0 -59
  34. data/spec/adapters/http_adapter.rb +0 -27
  35. data/spec/adapters/http_base_adapter.rb +0 -39
  36. data/spec/adapters/httparty_adapter.rb +0 -16
  37. data/spec/adapters/httpclient_adapter.rb +0 -31
  38. data/spec/adapters/net_http_adapter.rb +0 -21
  39. data/spec/adapters/open_uri_adapter.rb +0 -19
  40. data/spec/adapters/patron_adapter.rb +0 -36
  41. data/spec/adapters/typhoeus_adapter.rb +0 -28
  42. data/spec/configuration_spec.rb +0 -22
  43. data/spec/lib/http_client_spec.rb +0 -15
  44. data/spec/lib/http_log_spec.rb +0 -320
  45. data/spec/log/.gitkeep +0 -0
  46. data/spec/spec_helper.rb +0 -45
  47. data/spec/support/index.html +0 -8
  48. data/spec/support/index.html.gz +0 -0
  49. data/spec/support/log4r.yml +0 -17
  50. data/spec/support/not_gzipped.html.gz +0 -8
  51. data/spec/support/shared_examples.rb +0 -79
  52. data/spec/support/test.bin +0 -0
  53. data/spec/support/test.pdf +0 -198
  54. data/spec/support/test_server.rb +0 -34
  55. data/spec/support/utf8-invalid.html +0 -0
  56. data/spec/support/utf8.html +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd063a60dafd4b6bcc29fa80d877f8bd650375f4633fb0f0bc4398075677b583
4
- data.tar.gz: 136a0fe30f033ad30f8867e327e18109255735b371cc9ab5a7d8e973d3b6e201
3
+ metadata.gz: 9ce910d7c04847c28f836de26906c0c50aa4642b553b298d15067bf6ee25db56
4
+ data.tar.gz: 8600e756f28e033b6e39ac91235f69bf772fe467b05040d88c02ce54e982ef0b
5
5
  SHA512:
6
- metadata.gz: d4de29f881f37f1149400324e8c97de77dd7d7ef21e983dd513e411ae4c5b3d67d886807accdfddccbcc388d03e242e4597457d42783525e8ddcf8dbf9064799
7
- data.tar.gz: 46df8a4e8dff1054f60b8545db22df35159c20ee0ab405d78795d476cc990efffd7d8cfe635f7641667d78cbac38fc0d5709ae8e802d040989356a883d3a4cd8
6
+ metadata.gz: 6a76055079c7bfa4a229fda0861f90276c94cb64859ae7ce3c269e86656f3ad69fb3d5e8ed7eb2b82e9fe1facb0a73619951f099aafbbac7b0b503a52eb21046
7
+ data.tar.gz: f541eec119d175cc50563bc2673d60dae6246a43bcf570f9fe4db9a6295697ea94f7ffddb709aa8c9d0264f274f48c42bca3322d3364c1edd47298a984421363
@@ -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
  [![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)
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.4.
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 the values of sensitive requestparameters
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)
@@ -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 = `git ls-files`.split("\n")
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', ['~> 3.0']
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
@@ -45,7 +45,8 @@ if defined?(Excon)
45
45
  response_headers: headers,
46
46
  benchmark: bm,
47
47
  encoding: headers['Content-Encoding'],
48
- content_type: headers['Content-Type']
48
+ content_type: headers['Content-Type'],
49
+ mask_body: HttpLog.masked_body_url?(url)
49
50
  )
50
51
  result
51
52
  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
- if HttpLog.url_approved?(req.uri)
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: req.uri,
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
- if HttpLog.url_approved?(req.header.request_uri)
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: req.header.request_uri,
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(hort, port)`
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 = true
28
- @compact_log = false
29
- @json_log = false
30
- @logger = Logger.new($stdout)
31
- @severity = Logger::Severity::DEBUG
32
- @prefix = LOG_PREFIX
33
- @log_connect = true
34
- @log_request = true
35
- @log_headers = false
36
- @log_data = true
37
- @log_status = true
38
- @log_response = true
39
- @log_benchmark = true
40
- @url_whitelist_pattern = nil
41
- @url_blacklist_pattern = nil
42
- @color = false
43
- @prefix_data_lines = false
44
- @prefix_response_lines = false
45
- @prefix_line_numbers = false
46
- @filter_parameters = []
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
@@ -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.log(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,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
- raise BodyParsingError, "(not showing binary data)"
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 reponse body is not available here.
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.gsub(/(#{key})=[^&]+/i, "#{key}=#{PARAM_MASK}")
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]