rack-traffic-logger 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
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