resurfaceio-logger 1.8.4 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/resurfaceio/all.rb +10 -2
- data/lib/resurfaceio/base_logger.rb +1 -1
- data/lib/resurfaceio/http_logger.rb +98 -7
- data/lib/resurfaceio/http_logger_for_rack.rb +1 -2
- data/lib/resurfaceio/http_logger_for_rails.rb +1 -2
- data/lib/resurfaceio/{http_message_impl.rb → http_message.rb} +1 -1
- data/lib/resurfaceio/http_request_impl.rb +5 -0
- data/lib/resurfaceio/http_rule.rb +15 -0
- data/lib/resurfaceio/http_rules.rb +130 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95113134bbf15fba0ef898a514d24a24dd98da6e
|
4
|
+
data.tar.gz: 998377326921781e89c99e9905ebef0c6f932fc8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d64d21c53d46de1336ac33fc81f71e3ed1354709c8ec91fc239ed964c1e7d73fa7fc484b42223077836b6a2be5bdf9ad851d406c2c1cf72b82d8b2b00c544569
|
7
|
+
data.tar.gz: d7bac6e321c7d292e2190f15500e4df3e6d18dd0458bb221c565573fea7a19073501261d093c1dbef45f684bc2882d74720b981b84b68f6817e6653a0beb1c62
|
data/lib/resurfaceio/all.rb
CHANGED
@@ -5,9 +5,11 @@ require 'resurfaceio/base_logger'
|
|
5
5
|
require 'resurfaceio/http_logger'
|
6
6
|
require 'resurfaceio/http_logger_for_rack'
|
7
7
|
require 'resurfaceio/http_logger_for_rails'
|
8
|
-
require 'resurfaceio/
|
8
|
+
require 'resurfaceio/http_message'
|
9
9
|
require 'resurfaceio/http_request_impl'
|
10
10
|
require 'resurfaceio/http_response_impl'
|
11
|
+
require 'resurfaceio/http_rule'
|
12
|
+
require 'resurfaceio/http_rules'
|
11
13
|
require 'resurfaceio/usage_loggers'
|
12
14
|
|
13
15
|
module Resurfaceio
|
@@ -24,7 +26,7 @@ module Resurfaceio
|
|
24
26
|
class HttpLoggerForRails < HttpLoggerForRails
|
25
27
|
end
|
26
28
|
|
27
|
-
class
|
29
|
+
class HttpMessage < HttpMessage
|
28
30
|
end
|
29
31
|
|
30
32
|
class HttpRequestImpl < HttpRequestImpl
|
@@ -33,6 +35,12 @@ module Resurfaceio
|
|
33
35
|
class HttpResponseImpl < HttpResponseImpl
|
34
36
|
end
|
35
37
|
|
38
|
+
class HttpRule < HttpRule
|
39
|
+
end
|
40
|
+
|
41
|
+
class HttpRules < HttpRules
|
42
|
+
end
|
43
|
+
|
36
44
|
class UsageLoggers < UsageLoggers
|
37
45
|
end
|
38
46
|
|
@@ -3,30 +3,121 @@
|
|
3
3
|
|
4
4
|
require 'json'
|
5
5
|
require 'resurfaceio/base_logger'
|
6
|
-
require 'resurfaceio/
|
6
|
+
require 'resurfaceio/http_message'
|
7
|
+
require 'resurfaceio/http_rule'
|
8
|
+
require 'resurfaceio/http_rules'
|
7
9
|
|
8
10
|
class HttpLogger < BaseLogger
|
9
11
|
|
10
12
|
AGENT = 'http_logger.rb'.freeze
|
11
13
|
|
14
|
+
@@default_rules = HttpRules.strict_rules
|
15
|
+
|
16
|
+
def self.default_rules
|
17
|
+
@@default_rules
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.default_rules=(val)
|
21
|
+
@@default_rules = val.gsub(/^\s*include default\s*$/, '')
|
22
|
+
end
|
23
|
+
|
12
24
|
def self.string_content_type?(s)
|
13
25
|
!s.nil? && !(s =~ /^(text\/(html|plain|xml))|(application\/(json|soap|xml|x-www-form-urlencoded))/i).nil?
|
14
26
|
end
|
15
27
|
|
16
28
|
def initialize(options = {})
|
17
29
|
super(AGENT, options)
|
30
|
+
|
31
|
+
# read rules from param or defaults
|
32
|
+
if options.respond_to?(:has_key?) && options.has_key?(:rules)
|
33
|
+
@rules = options[:rules].gsub(/^\s*include default\s*$/, @@default_rules)
|
34
|
+
@rules = @@default_rules unless @rules.strip.length > 0
|
35
|
+
else
|
36
|
+
@rules = @@default_rules
|
37
|
+
end
|
38
|
+
|
39
|
+
# parse and break rules out by verb
|
40
|
+
prs = HttpRules.parse(@rules)
|
41
|
+
@rules_allow_http_url = prs.select {|r| 'allow_http_url' == r.verb}.length > 0
|
42
|
+
@rules_copy_session_field = prs.select {|r| 'copy_session_field' == r.verb}
|
43
|
+
@rules_remove = prs.select {|r| 'remove' == r.verb}
|
44
|
+
@rules_remove_if = prs.select {|r| 'remove_if' == r.verb}
|
45
|
+
@rules_remove_if_found = prs.select {|r| 'remove_if_found' == r.verb}
|
46
|
+
@rules_remove_unless = prs.select {|r| 'remove_unless' == r.verb}
|
47
|
+
@rules_remove_unless_found = prs.select {|r| 'remove_unless_found' == r.verb}
|
48
|
+
@rules_replace = prs.select {|r| 'replace' == r.verb}
|
49
|
+
@rules_sample = prs.select {|r| 'sample' == r.verb}
|
50
|
+
@rules_stop = prs.select {|r| 'stop' == r.verb}
|
51
|
+
@rules_stop_if = prs.select {|r| 'stop_if' == r.verb}
|
52
|
+
@rules_stop_if_found = prs.select {|r| 'stop_if_found' == r.verb}
|
53
|
+
@rules_stop_unless = prs.select {|r| 'stop_unless' == r.verb}
|
54
|
+
@rules_stop_unless_found = prs.select {|r| 'stop_unless_found' == r.verb}
|
55
|
+
@skip_compression = prs.select {|r| 'skip_compression' == r.verb}.length > 0
|
56
|
+
@skip_submission = prs.select {|r| 'skip_submission' == r.verb}.length > 0
|
57
|
+
|
58
|
+
# finish validating rules
|
59
|
+
raise RuntimeError.new('Multiple sample rules') if @rules_sample.length > 1
|
60
|
+
unless @url.nil? || @url.start_with?('https') || @rules_allow_http_url
|
61
|
+
@enableable = false
|
62
|
+
@enabled = false
|
63
|
+
end
|
18
64
|
end
|
19
65
|
|
20
|
-
def
|
21
|
-
|
22
|
-
message << ['agent', @agent]
|
23
|
-
message << ['version', @version]
|
24
|
-
message << ['now', now.nil? ? (Time.now.to_f * 1000).floor.to_s : now]
|
25
|
-
JSON.generate message
|
66
|
+
def rules
|
67
|
+
@rules
|
26
68
|
end
|
27
69
|
|
28
70
|
def log(request, response, response_body = nil, request_body = nil)
|
29
71
|
!enabled? || submit(format(request, response, response_body, request_body))
|
30
72
|
end
|
31
73
|
|
74
|
+
def format(request, response, response_body = nil, request_body = nil, now = nil)
|
75
|
+
details = HttpMessage.build(request, response, response_body, request_body)
|
76
|
+
|
77
|
+
# copy data from session if configured
|
78
|
+
unless @rules_copy_session_field.empty?
|
79
|
+
ssn = request.session
|
80
|
+
if !ssn.nil? && ssn.respond_to?(:keys)
|
81
|
+
@rules_copy_session_field.each do |r|
|
82
|
+
ssn.keys.each {|d| (details << ["session_field:#{d}", ssn[d].to_s]) if r.param1.match(d)}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# quit early based on stop rules if configured
|
88
|
+
@rules_stop.each {|r| details.each {|d| return nil if r.scope.match(d[0])}}
|
89
|
+
@rules_stop_if_found.each {|r| details.each {|d| return nil if r.scope.match(d[0]) && r.param1.match(d[1])}}
|
90
|
+
@rules_stop_if.each {|r| details.each {|d| return nil if r.scope.match(d[0]) && r.param1.match(d[1])}}
|
91
|
+
passed = 0
|
92
|
+
@rules_stop_unless_found.each {|r| details.each {|d| passed += 1 if r.scope.match(d[0]) && r.param1.match(d[1])}}
|
93
|
+
return nil if passed != @rules_stop_unless_found.length
|
94
|
+
passed = 0
|
95
|
+
@rules_stop_unless.each {|r| details.each {|d| passed += 1 if r.scope.match(d[0]) && r.param1.match(d[1])}}
|
96
|
+
return nil if passed != @rules_stop_unless.length
|
97
|
+
|
98
|
+
# do sampling if configured
|
99
|
+
return nil if !@rules_sample[0].nil? && (rand * 100 >= @rules_sample[0].param1)
|
100
|
+
|
101
|
+
# winnow sensitive details based on remove rules if configured
|
102
|
+
@rules_remove.each {|r| details.delete_if {|d| r.scope.match(d[0])}}
|
103
|
+
@rules_remove_unless_found.each {|r| details.delete_if {|d| r.scope.match(d[0]) && !r.param1.match(d[1])}}
|
104
|
+
@rules_remove_if_found.each {|r| details.delete_if {|d| r.scope.match(d[0]) && r.param1.match(d[1])}}
|
105
|
+
@rules_remove_unless.each {|r| details.delete_if {|d| r.scope.match(d[0]) && !r.param1.match(d[1])}}
|
106
|
+
@rules_remove_if.each {|r| details.delete_if {|d| r.scope.match(d[0]) && r.param1.match(d[1])}}
|
107
|
+
return nil if details.empty?
|
108
|
+
|
109
|
+
# mask sensitive details based on replace rules if configured
|
110
|
+
@rules_replace.each {|r| details.each {|d| d[1] = d[1].gsub(r.param1, r.param2) if r.scope.match(d[0])}}
|
111
|
+
|
112
|
+
# remove any details with empty values
|
113
|
+
details.delete_if {|d| '' == d[1]}
|
114
|
+
return nil if details.empty?
|
115
|
+
|
116
|
+
# finish message
|
117
|
+
details << ['now', now.nil? ? (Time.now.to_f * 1000).floor.to_s : now]
|
118
|
+
details << ['agent', @agent]
|
119
|
+
details << ['version', @version]
|
120
|
+
JSON.generate details
|
121
|
+
end
|
122
|
+
|
32
123
|
end
|
@@ -21,8 +21,7 @@ class HttpLoggerForRack # http://rack.rubyforge.org/doc/SPEC.html
|
|
21
21
|
response = Rack::Response.new(body, status, headers)
|
22
22
|
if HttpLogger::string_content_type?(response.content_type)
|
23
23
|
request = Rack::Request.new(env)
|
24
|
-
|
25
|
-
@logger.submit(message)
|
24
|
+
@logger.submit(@logger.format(request, response))
|
26
25
|
end
|
27
26
|
end
|
28
27
|
[status, headers, body]
|
@@ -20,8 +20,7 @@ class HttpLoggerForRails
|
|
20
20
|
response = controller.response
|
21
21
|
status = response.status
|
22
22
|
if (status < 300 || status == 302) && HttpLogger::string_content_type?(response.content_type)
|
23
|
-
|
24
|
-
@logger.submit(message)
|
23
|
+
@logger.submit(@logger.format(request, response))
|
25
24
|
end
|
26
25
|
end
|
27
26
|
end
|
@@ -7,6 +7,7 @@ class HttpRequestImpl
|
|
7
7
|
@form_hash = Hash.new
|
8
8
|
@headers = Hash.new
|
9
9
|
@query_hash = Hash.new
|
10
|
+
@session = Hash.new
|
10
11
|
end
|
11
12
|
|
12
13
|
def add_header(key, value)
|
@@ -40,6 +41,10 @@ class HttpRequestImpl
|
|
40
41
|
@query_hash
|
41
42
|
end
|
42
43
|
|
44
|
+
def session
|
45
|
+
@session
|
46
|
+
end
|
47
|
+
|
43
48
|
attr_accessor :request_method
|
44
49
|
attr_accessor :url
|
45
50
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# © 2016-2018 Resurface Labs LLC
|
3
|
+
|
4
|
+
class HttpRule
|
5
|
+
|
6
|
+
def initialize(verb, scope = nil, param1 = nil, param2 = nil)
|
7
|
+
@verb = verb
|
8
|
+
@scope = scope
|
9
|
+
@param1 = param1
|
10
|
+
@param2 = param2
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :verb, :scope, :param1, :param2
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# © 2016-2018 Resurface Labs LLC
|
3
|
+
|
4
|
+
class HttpRules
|
5
|
+
|
6
|
+
def self.debug_rules
|
7
|
+
"allow_http_url\ncopy_session_field /.*/\n"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.standard_rules
|
11
|
+
%q(/request_header:cookie|response_header:set-cookie/ remove
|
12
|
+
/(request|response)_body|request_param/ replace /[a-zA-Z0-9.!#$%&’*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)/, /x@y.com/
|
13
|
+
/request_body|request_param|response_body/ replace /[0-9\.\-\/]{9,}/, /xyxy/
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.strict_rules
|
18
|
+
%q(/request_url/ replace /([^\?;]+).*/, !\\\\1!
|
19
|
+
/request_body|response_body|request_param:.*|request_header:(?!user-agent).*|response_header:(?!(content-length)|(content-type)).*/ remove
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.parse(rules)
|
24
|
+
result = []
|
25
|
+
unless rules.nil?
|
26
|
+
rules = rules.gsub(/^\s*include debug\s*$/, debug_rules)
|
27
|
+
rules = rules.gsub(/^\s*include standard\s*$/, standard_rules)
|
28
|
+
rules = rules.gsub(/^\s*include strict\s*$/, strict_rules)
|
29
|
+
rules.each_line do |rule|
|
30
|
+
parsed = parse_rule(rule)
|
31
|
+
result << parsed unless parsed.nil?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
result
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.parse_rule(r)
|
38
|
+
if r.nil? || r.match(REGEX_BLANK_OR_COMMENT)
|
39
|
+
nil
|
40
|
+
elsif r.match(REGEX_ALLOW_HTTP_URL)
|
41
|
+
HttpRule.new('allow_http_url')
|
42
|
+
elsif (m = r.match(REGEX_COPY_SESSION_FIELD))
|
43
|
+
HttpRule.new('copy_session_field', nil, parse_regex(r, m[1]))
|
44
|
+
elsif (m = r.match(REGEX_REMOVE))
|
45
|
+
HttpRule.new('remove', parse_regex(r, m[1]))
|
46
|
+
elsif (m = r.match(REGEX_REMOVE_IF))
|
47
|
+
HttpRule.new('remove_if', parse_regex(r, m[1]), parse_regex(r, m[2]))
|
48
|
+
elsif (m = r.match(REGEX_REMOVE_IF_FOUND))
|
49
|
+
HttpRule.new('remove_if_found', parse_regex(r, m[1]), parse_regex_find(r, m[2]))
|
50
|
+
elsif (m = r.match(REGEX_REMOVE_UNLESS))
|
51
|
+
HttpRule.new('remove_unless', parse_regex(r, m[1]), parse_regex(r, m[2]))
|
52
|
+
elsif (m = r.match(REGEX_REMOVE_UNLESS_FOUND))
|
53
|
+
HttpRule.new('remove_unless_found', parse_regex(r, m[1]), parse_regex_find(r, m[2]))
|
54
|
+
elsif (m = r.match(REGEX_REPLACE))
|
55
|
+
HttpRule.new('replace', parse_regex(r, m[1]), parse_regex_find(r, m[2]), parse_string(r, m[3]))
|
56
|
+
elsif (m = r.match(REGEX_SAMPLE))
|
57
|
+
m1 = m[1].to_i
|
58
|
+
raise RuntimeError.new("Invalid sample percent: #{m1}") if m1 < 1 || m1 > 99
|
59
|
+
HttpRule.new('sample', nil, m1)
|
60
|
+
elsif r.match(REGEX_SKIP_COMPRESSION)
|
61
|
+
HttpRule.new('skip_compression')
|
62
|
+
elsif r.match(REGEX_SKIP_SUBMISSION)
|
63
|
+
HttpRule.new('skip_submission')
|
64
|
+
elsif (m = r.match(REGEX_STOP))
|
65
|
+
HttpRule.new('stop', parse_regex(r, m[1]))
|
66
|
+
elsif (m = r.match(REGEX_STOP_IF))
|
67
|
+
HttpRule.new('stop_if', parse_regex(r, m[1]), parse_regex(r, m[2]))
|
68
|
+
elsif (m = r.match(REGEX_STOP_IF_FOUND))
|
69
|
+
HttpRule.new('stop_if_found', parse_regex(r, m[1]), parse_regex_find(r, m[2]))
|
70
|
+
elsif (m = r.match(REGEX_STOP_UNLESS))
|
71
|
+
HttpRule.new('stop_unless', parse_regex(r, m[1]), parse_regex(r, m[2]))
|
72
|
+
elsif (m = r.match(REGEX_STOP_UNLESS_FOUND))
|
73
|
+
HttpRule.new('stop_unless_found', parse_regex(r, m[1]), parse_regex_find(r, m[2]))
|
74
|
+
else
|
75
|
+
raise RuntimeError.new("Invalid rule: #{r}")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
def self.parse_regex(r, regex)
|
82
|
+
str = parse_string(r, regex)
|
83
|
+
raise RuntimeError.new("Invalid regex (#{regex}) in rule: #{r}") if '*' == str || '+' == str || '?' == str
|
84
|
+
str = "^#{str}" unless str.start_with?('^')
|
85
|
+
str = "#{str}$" unless str.end_with?('$')
|
86
|
+
begin
|
87
|
+
return Regexp.compile(str)
|
88
|
+
rescue RegexpError
|
89
|
+
raise RuntimeError.new("Invalid regex (#{regex}) in rule: #{r}")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.parse_regex_find(r, regex)
|
94
|
+
begin
|
95
|
+
return Regexp.compile(parse_string(r, regex))
|
96
|
+
rescue RegexpError
|
97
|
+
raise RuntimeError.new("Invalid regex (#{regex}) in rule: #{r}")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.parse_string(r, str)
|
102
|
+
%w(~ ! % | /).each do |sep|
|
103
|
+
if (m = str.match(/^[#{sep}](.*)[#{sep}]$/))
|
104
|
+
m1 = m[1]
|
105
|
+
raise RuntimeError.new("Unescaped separator (#{sep}) in rule: #{r}") if m1.match(/^[#{sep}].*|.*[^\\][#{sep}].*/)
|
106
|
+
return m1.gsub("\\#{sep}", sep)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
raise RuntimeError.new("Invalid expression (#{str}) in rule: #{r}")
|
110
|
+
end
|
111
|
+
|
112
|
+
REGEX_ALLOW_HTTP_URL = /^\s*allow_http_url\s*(#.*)?$/.freeze
|
113
|
+
REGEX_BLANK_OR_COMMENT = /^\s*([#].*)*$/.freeze
|
114
|
+
REGEX_COPY_SESSION_FIELD = /^\s*copy_session_field\s+([~!%|\/].+[~!%|\/])\s*(#.*)?$/.freeze
|
115
|
+
REGEX_REMOVE = /^\s*([~!%|\/].+[~!%|\/])\s*remove\s*(#.*)?$/.freeze
|
116
|
+
REGEX_REMOVE_IF = /^\s*([~!%|\/].+[~!%|\/])\s*remove_if\s+([~!%|\/].+[~!%|\/])\s*(#.*)?$/.freeze
|
117
|
+
REGEX_REMOVE_IF_FOUND = /^\s*([~!%|\/].+[~!%|\/])\s*remove_if_found\s+([~!%|\/].+[~!%|\/])\s*(#.*)?$/.freeze
|
118
|
+
REGEX_REMOVE_UNLESS = /^\s*([~!%|\/].+[~!%|\/])\s*remove_unless\s+([~!%|\/].+[~!%|\/])\s*(#.*)?$/.freeze
|
119
|
+
REGEX_REMOVE_UNLESS_FOUND = /^\s*([~!%|\/].+[~!%|\/])\s*remove_unless_found\s+([~!%|\/].+[~!%|\/])\s*(#.*)?$/.freeze
|
120
|
+
REGEX_REPLACE = /^\s*([~!%|\/].+[~!%|\/])\s*replace[\s]+([~!%|\/].+[~!%|\/]),[\s]+([~!%|\/].*[~!%|\/])\s*(#.*)?$/.freeze
|
121
|
+
REGEX_SAMPLE = /^\s*sample\s+(\d+)\s*(#.*)?$/.freeze
|
122
|
+
REGEX_SKIP_COMPRESSION = /^\s*skip_compression\s*(#.*)?$/.freeze
|
123
|
+
REGEX_SKIP_SUBMISSION = /^\s*skip_submission\s*(#.*)?$/.freeze
|
124
|
+
REGEX_STOP = /^\s*([~!%|\/].+[~!%|\/])\s*stop\s*(#.*)?$/.freeze
|
125
|
+
REGEX_STOP_IF = /^\s*([~!%|\/].+[~!%|\/])\s*stop_if\s+([~!%|\/].+[~!%|\/])\s*(#.*)?$/.freeze
|
126
|
+
REGEX_STOP_IF_FOUND = /^\s*([~!%|\/].+[~!%|\/])\s*stop_if_found\s+([~!%|\/].+[~!%|\/])\s*(#.*)?$/.freeze
|
127
|
+
REGEX_STOP_UNLESS = /^\s*([~!%|\/].+[~!%|\/])\s*stop_unless\s+([~!%|\/].+[~!%|\/])\s*(#.*)?$/.freeze
|
128
|
+
REGEX_STOP_UNLESS_FOUND = /^\s*([~!%|\/].+[~!%|\/])\s*stop_unless_found\s+([~!%|\/].+[~!%|\/])\s*(#.*)?$/.freeze
|
129
|
+
|
130
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: resurfaceio-logger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- RobDickinson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-06-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -77,9 +77,11 @@ files:
|
|
77
77
|
- lib/resurfaceio/http_logger.rb
|
78
78
|
- lib/resurfaceio/http_logger_for_rack.rb
|
79
79
|
- lib/resurfaceio/http_logger_for_rails.rb
|
80
|
-
- lib/resurfaceio/
|
80
|
+
- lib/resurfaceio/http_message.rb
|
81
81
|
- lib/resurfaceio/http_request_impl.rb
|
82
82
|
- lib/resurfaceio/http_response_impl.rb
|
83
|
+
- lib/resurfaceio/http_rule.rb
|
84
|
+
- lib/resurfaceio/http_rules.rb
|
83
85
|
- lib/resurfaceio/usage_loggers.rb
|
84
86
|
homepage: https://github.com/resurfaceio/logger-ruby
|
85
87
|
licenses:
|