rack-traffic-logger 0.2.4 → 0.2.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 14b29cb8230446091b0ff4d48a732e4cb907381c
4
- data.tar.gz: 28bf54115618158d68942804a366894a3cc59537
3
+ metadata.gz: d4602c2181807d0305806dda404a27aca2715695
4
+ data.tar.gz: 5fc0fa9b637279c1be407da67f3af6b7e5667190
5
5
  SHA512:
6
- metadata.gz: f3deff06a541bab57142e46c2f11a077c4dc9ea662e11585f7f4df00836a8d50a45a65f4d9317c93afca930382ef694be3fa56404553f70079d39b455763ede1
7
- data.tar.gz: 71d3bebc92b6b9e4587a25826f18813feb76a901e1c990b3b578dd06945137d5c2e71a632f994882a09fe6b32a8e25a89a56820f531461f881d8050b09de6116
6
+ metadata.gz: 0c35048dcf21d51d2b727007d0e311dee8cfa4bc42e65fc8302c44c9f6d1b8cf0271da2239cf68e6d54aa7c369292e0b56b048748e2ada9a148ab1e6a5b5a9e7
7
+ data.tar.gz: 6a7fd9b6535e0976e9bd28b3eed605eda8346c166f421dab5ef1f7b5c06e69e1ecbe0ad273e16ee59ae81f82b1bb0440e56ab547347cd6437647864ea1198722
data/README.md CHANGED
@@ -215,3 +215,26 @@ use Rack::TrafficLogger, 'file.log', Rack::TrafficLogger::Formatter::JSON.new(pr
215
215
  ```
216
216
 
217
217
  Note that if you do, log parsers may have a hard time understanding your logs if they expect each event to be on a single line. If you think this could be an issue, use the first method instead.
218
+
219
+ ## Usage with Faraday
220
+
221
+ If you use [Faraday](https://github.com/lostisland/faraday), you can log outbound HTTP traffic using the included middleware adapter.
222
+
223
+ ```ruby
224
+ Faraday.new(url: 'http://localhost') do |builder|
225
+ builder.use Rack::TrafficLogger::FaradayAdapter, Rails.root.join('log/http_out.log').to_s
226
+ builder.adapter Faraday.default_adapter
227
+ end
228
+ ```
229
+
230
+ You can also use express setup:
231
+
232
+ ```ruby
233
+ Faraday.new(url: 'http://localhost') do |builder|
234
+ Rack::TrafficLogger::FaradayAdapter.use on: builder,
235
+ filter: ENV['LOG_OUTBOUND_HTTP'],
236
+ formatter: Rack::TrafficLogger::Formatter::JSON.new,
237
+ log_path: Rails.root.join('log/http_out.log').to_s
238
+ builder.adapter Faraday.default_adapter
239
+ end
240
+ ```
@@ -5,6 +5,8 @@ require_relative 'traffic_logger/stream_simulator'
5
5
  require_relative 'traffic_logger/formatter'
6
6
  require_relative 'traffic_logger/reader'
7
7
  require_relative 'traffic_logger/express_setup'
8
+ require_relative 'traffic_logger/request'
9
+ require_relative 'traffic_logger/faraday_adapter'
8
10
 
9
11
  require 'json'
10
12
  require 'securerandom'
@@ -25,7 +27,7 @@ module Rack
25
27
  REMOTE_ADDR
26
28
  ]
27
29
 
28
- attr_reader :app, :options
30
+ attr_reader :options
29
31
 
30
32
  def initialize(app, log_path, *options)
31
33
  @app = app
@@ -35,7 +37,15 @@ module Rack
35
37
  end
36
38
 
37
39
  def call(env)
38
- Request.new(self).call env
40
+ request = Request.new(self)
41
+ request.start env
42
+ response = nil
43
+ begin
44
+ response = @app.call(env)
45
+ ensure
46
+ request.finish response
47
+ end
48
+ response
39
49
  end
40
50
 
41
51
  def log(hash)
@@ -50,108 +60,5 @@ module Rack
50
60
  end
51
61
  end
52
62
 
53
- private
54
-
55
- class Request
56
-
57
- def initialize(logger)
58
- @logger = logger
59
- @id = SecureRandom.hex 4
60
- @started_at = Time.now
61
- end
62
-
63
- def call(env)
64
- @verb = env['REQUEST_METHOD']
65
- @env = env
66
- begin
67
- response = @logger.app.call(env)
68
- ensure
69
- @code = Array === response ? response.first.to_i : 0
70
- @options = @logger.options.for(@verb, @code)
71
- if @options.basic?
72
- log_request env
73
- log_response env, response if @code > 0
74
- end
75
- end
76
- end
77
-
78
- private
79
-
80
- BASIC_AUTH_PATTERN = /^basic ([a-z\d+\/]+={0,2})$/i
81
-
82
- def log_request(env)
83
- log 'request' do |hash|
84
- if @options.request_headers?
85
- hash.merge! env.reject { |_, v| v.respond_to? :read }
86
- else
87
- hash.merge! env.select { |k, _| BASIC_ENV_PROPERTIES.include? k }
88
- end
89
-
90
- hash['BASIC_AUTH_USERINFO'] = $1.unpack('m').first.split(':', 2) if hash['HTTP_AUTHORIZATION'] =~ BASIC_AUTH_PATTERN
91
-
92
- input = env['rack.input']
93
- if input && @options.request_bodies?
94
- add_body_to_hash input.tap(&:rewind).read, env['CONTENT_ENCODING'] || env['HTTP_CONTENT_ENCODING'], hash
95
- input.rewind
96
- end
97
- end
98
- end
99
-
100
- def log_response(env, response)
101
- code, headers, body = response
102
- code = code.to_i
103
- headers = HeaderHash.new(headers) if @options.response_headers? || @options.response_bodies?
104
- log 'response' do |hash|
105
- hash['http_version'] = env['HTTP_VERSION'] || 'HTTP/1.1'
106
- hash['status_code'] = code
107
- hash['status_name'] = Utils::HTTP_STATUS_CODES[code]
108
- hash['headers'] = headers if @options.response_headers?
109
- add_body_to_hash get_real_body(body), headers['Content-Encoding'], hash if @options.response_bodies?
110
- end
111
- end
112
-
113
- # Rack allows response bodies to be a few different things. This method
114
- # ensures we get a string back.
115
- def get_real_body(body)
116
-
117
- # For bodies representing temporary files
118
- body = File.open(body.path, 'rb') { |f| f.read } if body.respond_to? :path
119
-
120
- # For bodies representing streams
121
- body = body.read.tap { body.rewind } if body.respond_to? :read
122
-
123
- # When body is an array (the common scenario)
124
- body = body.join if body.respond_to? :join
125
-
126
- # When body is a proxy
127
- body = body.body while Rack::BodyProxy === body
128
-
129
- # It should be a string now. Just in case it's not...
130
- body.to_s
131
-
132
- end
133
-
134
- def add_body_to_hash(body, encoding, hash)
135
- body = Zlib::GzipReader.new(StringIO.new body).read if encoding == 'gzip'
136
- body.force_encoding 'UTF-8'
137
- if body.valid_encoding?
138
- hash['body'] = body
139
- else
140
- hash['body_base64'] = [body].pack 'm0'
141
- end
142
- end
143
-
144
- def log(event)
145
- hash = {
146
- timestamp: Time.now,
147
- request_log_id: @id,
148
- event: event
149
- }
150
- yield hash rescue hash.merge! error: $!
151
- @logger.log hash
152
- end
153
-
154
- end
155
-
156
63
  end
157
64
  end
@@ -1,10 +1,10 @@
1
1
  module Rack
2
2
  class TrafficLogger
3
3
 
4
- def self.use(on: nil, filter: '', log_path: '/dev/stdout', formatter: nil)
4
+ def self.use(on: nil, filter: true, log_path: '/dev/stdout', formatter: nil)
5
5
  filter = (filter || '').to_s.downcase.strip
6
6
  unless ['0', 'no', 'false', 'nil', 'none', '', 'off'].include? filter
7
- args = [Rack::TrafficLogger, log_path]
7
+ args = [self, log_path]
8
8
  args << formatter if formatter
9
9
  begin
10
10
  raise if %w[1 yes true normal basic minimal on].include? filter
@@ -0,0 +1,50 @@
1
+ module Rack
2
+ class TrafficLogger
3
+ # noinspection RubyStringKeysInHashInspection
4
+ class FaradayAdapter < TrafficLogger
5
+
6
+ def call(request_env)
7
+ rack_env = convert_request(request_env)
8
+ request = Request.new(self)
9
+ request.start rack_env
10
+ @app.call(request_env).on_complete do |response_env|
11
+ rack_response = convert_response(response_env)
12
+ request.finish rack_response
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def convert_request(faraday_env)
19
+ url = faraday_env.url
20
+ {
21
+ 'REQUEST_METHOD' => faraday_env.method.to_s.upcase,
22
+ 'SERVER_NAME' => url.host,
23
+ 'SERVER_PORT' => url.port,
24
+ 'PATH_INFO' => url.path,
25
+ 'HTTP_VERSION' => 'HTTP/1.1', # TODO: can this be obtained?
26
+ 'REMOTE_HOST' => 'localhost',
27
+ 'REMOTE_ADDR' => '127.0.0.1'
28
+ }.tap do |hash|
29
+ hash['HTTPS'] = 'on' if url.scheme == 'https'
30
+ hash['QUERY_STRING'] = url.query if url.query
31
+ hash.merge!(faraday_env.request_headers.map do |k, v|
32
+ k = k.gsub('-', '_').upcase
33
+ k = "HTTP_#{k}" unless k.start_with? 'CONTENT_'
34
+ [k, v]
35
+ end.to_h)
36
+ hash['rack.input'] = StringIO.new(faraday_env.body) if faraday_env.body
37
+ end
38
+ end
39
+
40
+ def convert_response(faraday_env)
41
+ [
42
+ faraday_env.status,
43
+ faraday_env.response_headers,
44
+ [faraday_env.body]
45
+ ]
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -3,7 +3,7 @@ require 'json'
3
3
  module Rack
4
4
  class TrafficLogger
5
5
  class Formatter
6
- class JSON < self
6
+ class JSON < Formatter
7
7
 
8
8
  def initialize(pretty_print: false)
9
9
  formatter = pretty_print ?
@@ -1,16 +1,17 @@
1
+ require 'time'
2
+
1
3
  module Rack
2
4
  class TrafficLogger
3
5
  class Formatter
4
- class Stream < self
6
+ class Stream < Formatter
5
7
 
6
8
  def initialize(**options)
7
9
  @simulator = StreamSimulator.new(**options)
8
10
  end
9
11
 
10
12
  def format(hash)
11
- time = hash[:timestamp]
12
- id = hash[:request_log_id]
13
- "@ #{time.strftime '%a %d %b \'%y %T'}.#{'%d' % (time.usec / 1e4)} ##{id}\n#{@simulator.format(hash)}\n\n"
13
+ time = Time.parse(hash['timestamp'])
14
+ "@ #{time.strftime '%a %d %b \'%y %T.%3N'} ##{hash['request_log_id']}\n#{@simulator.format(hash)}\n\n"
14
15
  end
15
16
 
16
17
  end
@@ -35,9 +35,6 @@ module Rack
35
35
  def writeline(line)
36
36
  begin
37
37
  hash = JSON.parse(line)
38
- hash[:timestamp] = Time.parse(hash['timestamp'])
39
- hash[:request_log_id] = hash['request_log_id']
40
- hash[:event] = hash['event']
41
38
  @output << @formatter.format(hash)
42
39
  rescue
43
40
  @output << line
@@ -0,0 +1,104 @@
1
+ module Rack
2
+ class TrafficLogger
3
+ class Request
4
+
5
+ def initialize(logger)
6
+ @logger = logger
7
+ @id = SecureRandom.hex 4
8
+ @started_at = Time.now
9
+ end
10
+
11
+ def start(env)
12
+ @verb = env['REQUEST_METHOD']
13
+ @env = env
14
+ end
15
+
16
+ def finish(response)
17
+ @code = Array === response ? response.first.to_i : 0
18
+ @options = @logger.options.for(@verb, @code)
19
+ if @options.basic?
20
+ log_request @env
21
+ log_response @env, response if @code > 0
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ BASIC_AUTH_PATTERN = /^basic ([a-z\d+\/]+={0,2})$/i
28
+
29
+ def log_request(env)
30
+ log 'request' do |hash|
31
+ if @options.request_headers?
32
+ hash.merge! env.reject { |_, v| v.respond_to? :read }
33
+ else
34
+ hash.merge! env.select { |k, _| BASIC_ENV_PROPERTIES.include? k }
35
+ end
36
+
37
+ hash['BASIC_AUTH_USERINFO'] = $1.unpack('m').first.split(':', 2) if hash['HTTP_AUTHORIZATION'] =~ BASIC_AUTH_PATTERN
38
+
39
+ input = env['rack.input']
40
+ if input && @options.request_bodies?
41
+ add_body_to_hash input.tap(&:rewind).read, env['CONTENT_ENCODING'] || env['HTTP_CONTENT_ENCODING'], hash
42
+ input.rewind
43
+ end
44
+ end
45
+ end
46
+
47
+ def log_response(env, response)
48
+ code, headers, body = response
49
+ code = code.to_i
50
+ headers = HeaderHash.new(headers) if @options.response_headers? || @options.response_bodies?
51
+ log 'response' do |hash|
52
+ hash['http_version'] = env['HTTP_VERSION'] || 'HTTP/1.1'
53
+ hash['status_code'] = code
54
+ hash['status_name'] = Utils::HTTP_STATUS_CODES[code]
55
+ hash['headers'] = headers if @options.response_headers?
56
+ add_body_to_hash get_real_body(body), headers['Content-Encoding'], hash if @options.response_bodies?
57
+ end
58
+ end
59
+
60
+ # Rack allows response bodies to be a few different things. This method
61
+ # ensures we get a string back.
62
+ def get_real_body(body)
63
+
64
+ # For bodies representing temporary files
65
+ body = File.open(body.path, 'rb') { |f| f.read } if body.respond_to? :path
66
+
67
+ # For bodies representing streams
68
+ body = body.read.tap { body.rewind } if body.respond_to? :read
69
+
70
+ # When body is an array (the common scenario)
71
+ body = body.join if body.respond_to? :join
72
+
73
+ # When body is a proxy
74
+ body = body.body while Rack::BodyProxy === body
75
+
76
+ # It should be a string now. Just in case it's not...
77
+ body.to_s
78
+
79
+ end
80
+
81
+ def add_body_to_hash(body, encoding, hash)
82
+ body = Zlib::GzipReader.new(StringIO.new body).read if encoding == 'gzip'
83
+ body.force_encoding 'UTF-8'
84
+ if body.valid_encoding?
85
+ hash['body'] = body
86
+ else
87
+ hash['body_base64'] = [body].pack 'm0'
88
+ end
89
+ end
90
+
91
+ def log(event)
92
+ # noinspection RubyStringKeysInHashInspection
93
+ hash = {
94
+ 'timestamp' => Time.now.strftime('%FT%T.%3N%:z'),
95
+ 'request_log_id' => @id,
96
+ 'event' => event
97
+ }
98
+ yield hash rescue hash.merge! error: $!
99
+ @logger.log hash
100
+ end
101
+
102
+ end
103
+ end
104
+ end
@@ -8,7 +8,7 @@ module Rack
8
8
  end
9
9
 
10
10
  def format(input)
11
- case input[:event]
11
+ case input['event']
12
12
  when 'request' then format_request input
13
13
  when 'response' then format_response input
14
14
  else nil
@@ -38,7 +38,7 @@ module Rack
38
38
  result = render REQUEST_TEMPLATES[@color],
39
39
  verb: input['REQUEST_METHOD'],
40
40
  path: input['PATH_INFO'],
41
- qs: (q = input['QUERY_STRING']).empty? ? '' : "?#{q}",
41
+ qs: (q = input['QUERY_STRING'] || '').empty? ? '' : "?#{q}",
42
42
  http: input['HTTP_VERSION'] || 'HTTP/1.1'
43
43
  result << format_headers(env_request_headers input)
44
44
  result << format_body(input, input['CONTENT_TYPE'] || input['HTTP_CONTENT_TYPE'])
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class TrafficLogger
3
- VERSION = '0.2.4'
3
+ VERSION = '0.2.5'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-traffic-logger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neil E. Pearson
@@ -38,6 +38,7 @@ files:
38
38
  - lib/rack/traffic_logger.rb
39
39
  - lib/rack/traffic_logger/echo.rb
40
40
  - lib/rack/traffic_logger/express_setup.rb
41
+ - lib/rack/traffic_logger/faraday_adapter.rb
41
42
  - lib/rack/traffic_logger/formatter.rb
42
43
  - lib/rack/traffic_logger/formatter/json.rb
43
44
  - lib/rack/traffic_logger/formatter/stream.rb
@@ -45,6 +46,7 @@ files:
45
46
  - lib/rack/traffic_logger/option_interpreter.rb
46
47
  - lib/rack/traffic_logger/option_interpreter/shorthand.rb
47
48
  - lib/rack/traffic_logger/reader.rb
49
+ - lib/rack/traffic_logger/request.rb
48
50
  - lib/rack/traffic_logger/stream_simulator.rb
49
51
  - lib/rack/traffic_logger/version.rb
50
52
  homepage: https://github.com/hx/rack-traffic-logger