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 +4 -4
- data/README.md +23 -0
- data/lib/rack/traffic_logger.rb +12 -105
- data/lib/rack/traffic_logger/express_setup.rb +2 -2
- data/lib/rack/traffic_logger/faraday_adapter.rb +50 -0
- data/lib/rack/traffic_logger/formatter/json.rb +1 -1
- data/lib/rack/traffic_logger/formatter/stream.rb +5 -4
- data/lib/rack/traffic_logger/reader.rb +0 -3
- data/lib/rack/traffic_logger/request.rb +104 -0
- data/lib/rack/traffic_logger/stream_simulator.rb +2 -2
- data/lib/rack/traffic_logger/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d4602c2181807d0305806dda404a27aca2715695
|
4
|
+
data.tar.gz: 5fc0fa9b637279c1be407da67f3af6b7e5667190
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
+
```
|
data/lib/rack/traffic_logger.rb
CHANGED
@@ -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 :
|
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)
|
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:
|
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 = [
|
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
|
@@ -1,16 +1,17 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
class TrafficLogger
|
3
5
|
class Formatter
|
4
|
-
class Stream <
|
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[
|
12
|
-
|
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[
|
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'])
|
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
|
+
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
|