resurfaceio-logger 1.10.0 → 2.0.0

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
  SHA256:
3
- metadata.gz: '081b638ef0c63585d851de54a9b30ef9b54de58cf68f880785c2e1a8d214cb54'
4
- data.tar.gz: a71c49ec92a27637f7d77b52177df39d0b197757a056154e5247f5e40aa350d1
3
+ metadata.gz: 78d67f05915dcaee82ef7e3809856549bf09d01ac1f26d32f4dcb02003d317f3
4
+ data.tar.gz: f3d3598661aed83877882554183991ef228ac92d3f875f087eafddd3221cfa59
5
5
  SHA512:
6
- metadata.gz: f2f2c6be12c8977756ab185cc34c64cc29b0956940139315a1d9c023f4df0756cbea1c3fdb8eaab9207070ddda63ee1dac34688d8272d1782d5664b531de94a3
7
- data.tar.gz: 8eed5ac226184687025edf8d3f213585b73d1577c8c6a5420f71b4bc5205add88470672e350746edef7e2a48bf3441276517fcb7ec176bdb16f88b21f9059c7a
6
+ metadata.gz: 5aa5de88c997daddb5f345e4e41d5a173e59adb449dec141ee015747a48ef2f82a7ec623a6b1630d913f5a018f72c4aa1907e310fa1a7808f5c5c579db0281d9
7
+ data.tar.gz: e998c80806e0488c0790e551285e3ae90b7897aa7e027019ad18fd9c9794f0bb37add6d5aaeddb8eb275e66da18b3b9e21b042af636009966d9b6773f200ae88
@@ -1,5 +1,5 @@
1
1
  # coding: utf-8
2
- # © 2016-2019 Resurface Labs Inc.
2
+ # © 2016-2020 Resurface Labs Inc.
3
3
 
4
4
  require 'resurfaceio/base_logger'
5
5
  require 'resurfaceio/http_logger'
@@ -1,5 +1,5 @@
1
1
  # coding: utf-8
2
- # © 2016-2019 Resurface Labs Inc.
2
+ # © 2016-2020 Resurface Labs Inc.
3
3
 
4
4
  require 'uri'
5
5
  require 'net/http'
@@ -51,7 +51,12 @@ class BaseLogger
51
51
  end
52
52
  end
53
53
 
54
+ # finalize internal properties
54
55
  @enableable = !@queue.nil? || !@url.nil?
56
+ @submit_failures = 0
57
+ @submit_failures_lock = Mutex.new
58
+ @submit_successes = 0
59
+ @submit_successes_lock = Mutex.new
55
60
  end
56
61
 
57
62
  def agent
@@ -102,10 +107,10 @@ class BaseLogger
102
107
 
103
108
  def submit(msg)
104
109
  if msg.nil? || @skip_submission || !enabled?
105
- true
110
+ # do nothing
106
111
  elsif @queue
107
112
  @queue << msg
108
- true
113
+ @submit_successes_lock.synchronize { @submit_successes += 1 }
109
114
  else
110
115
  begin
111
116
  @url_parsed ||= URI.parse(@url)
@@ -119,14 +124,26 @@ class BaseLogger
119
124
  request.body = Zlib::Deflate.deflate(msg)
120
125
  end
121
126
  response = @url_connection.request(request)
122
- response.code.to_i == 204
127
+ if response.code.to_i == 204
128
+ @submit_successes_lock.synchronize { @submit_successes += 1 }
129
+ else
130
+ @submit_failures_lock.synchronize { @submit_failures += 1 }
131
+ end
123
132
  rescue SocketError
133
+ @submit_failures_lock.synchronize { @submit_failures += 1 }
124
134
  @url_connection = nil
125
- false
126
135
  end
127
136
  end
128
137
  end
129
138
 
139
+ def submit_failures
140
+ @submit_failures
141
+ end
142
+
143
+ def submit_successes
144
+ @submit_successes
145
+ end
146
+
130
147
  def url
131
148
  @url
132
149
  end
@@ -1,5 +1,5 @@
1
1
  # coding: utf-8
2
- # © 2016-2019 Resurface Labs Inc.
2
+ # © 2016-2020 Resurface Labs Inc.
3
3
 
4
4
  require 'json'
5
5
  require 'resurfaceio/base_logger'
@@ -11,53 +11,20 @@ class HttpLogger < BaseLogger
11
11
 
12
12
  AGENT = 'http_logger.rb'.freeze
13
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
-
24
- def self.string_content_type?(s)
25
- !s.nil? && !(s =~ /^(text\/(html|plain|xml))|(application\/(json|soap|xml|x-www-form-urlencoded))/i).nil?
26
- end
27
-
28
14
  def initialize(options = {})
29
15
  super(AGENT, options)
30
16
 
31
- # read rules from param or defaults
17
+ # parse specified rules
32
18
  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
19
+ @rules = HttpRules.new(options[:rules])
35
20
  else
36
- @rules = @@default_rules
21
+ @rules = HttpRules.new(nil)
37
22
  end
38
23
 
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
24
+ # apply configuration rules
25
+ @skip_compression = @rules.skip_compression
26
+ @skip_submission = @rules.skip_submission
27
+ unless @url.nil? || @url.start_with?('https') || @rules.allow_http_url
61
28
  @enableable = false
62
29
  @enabled = false
63
30
  end
@@ -67,58 +34,21 @@ class HttpLogger < BaseLogger
67
34
  @rules
68
35
  end
69
36
 
70
- def log(request, response, response_body = nil, request_body = nil)
71
- !enabled? || submit(format(request, response, response_body, request_body))
72
- end
73
-
74
- def format(request, response, response_body = nil, request_body = nil, now = nil)
75
- details = HttpMessage.build(request, response, response_body, request_body)
37
+ def submit_if_passing(details)
38
+ # apply active rules
39
+ details = @rules.apply(details)
40
+ return nil if details.nil?
76
41
 
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]
42
+ # finalize message
118
43
  details << ['agent', @agent]
119
44
  details << ['host', @host]
120
45
  details << ['version', @version]
121
- JSON.generate details
46
+
47
+ # let's do this thing
48
+ submit(JSON.generate(details))
122
49
  end
123
50
 
51
+ def self.string_content_type?(s)
52
+ !s.nil? && !(s =~ /^(text\/(html|plain|xml))|(application\/(json|soap|xml|x-www-form-urlencoded))/i).nil?
53
+ end
124
54
  end
@@ -1,8 +1,9 @@
1
1
  # coding: utf-8
2
- # © 2016-2019 Resurface Labs Inc.
2
+ # © 2016-2020 Resurface Labs Inc.
3
3
 
4
4
  require 'rack'
5
5
  require 'resurfaceio/http_logger'
6
+ require 'resurfaceio/http_message'
6
7
 
7
8
  class HttpLoggerForRack # http://rack.rubyforge.org/doc/SPEC.html
8
9
 
@@ -21,7 +22,7 @@ class HttpLoggerForRack # http://rack.rubyforge.org/doc/SPEC.html
21
22
  response = Rack::Response.new(body, status, headers)
22
23
  if HttpLogger::string_content_type?(response.content_type)
23
24
  request = Rack::Request.new(env)
24
- @logger.submit(@logger.format(request, response))
25
+ HttpMessage.send(logger, request, response) # todo add timing details
25
26
  end
26
27
  end
27
28
  [status, headers, body]
@@ -1,7 +1,8 @@
1
1
  # coding: utf-8
2
- # © 2016-2019 Resurface Labs Inc.
2
+ # © 2016-2020 Resurface Labs Inc.
3
3
 
4
4
  require 'resurfaceio/http_logger'
5
+ require 'resurfaceio/http_message'
5
6
 
6
7
  class HttpLoggerForRails
7
8
 
@@ -20,7 +21,7 @@ class HttpLoggerForRails
20
21
  response = controller.response
21
22
  status = response.status
22
23
  if (status < 300 || status == 302) && HttpLogger::string_content_type?(response.content_type)
23
- @logger.submit(@logger.format(request, response))
24
+ HttpMessage.send(logger, request, response) # todo add timing details
24
25
  end
25
26
  end
26
27
  end
@@ -1,10 +1,33 @@
1
1
  # coding: utf-8
2
- # © 2016-2019 Resurface Labs Inc.
2
+ # © 2016-2020 Resurface Labs Inc.
3
3
 
4
4
  require 'json'
5
5
 
6
6
  class HttpMessage
7
7
 
8
+ def self.send(logger, request, response, response_body = nil, request_body = nil, now = nil, interval = nil)
9
+ return unless logger.enabled?
10
+
11
+ # copy details from request & response
12
+ message = build(request, response, response_body, request_body)
13
+
14
+ # copy details from active session
15
+ unless logger.rules.copy_session_field.empty?
16
+ ssn = request.session
17
+ if !ssn.nil? && ssn.respond_to?(:keys)
18
+ logger.rules.copy_session_field.each do |r|
19
+ ssn.keys.each {|d| (message << ["session_field:#{d}", ssn[d].to_s]) if r.param1.match(d)}
20
+ end
21
+ end
22
+ end
23
+
24
+ # add timing details
25
+ message << ['now', now.nil? ? (Time.now.to_f * 1000).floor.to_s : now]
26
+ message << ['interval', interval] unless interval.nil?
27
+
28
+ logger.submit_if_passing(message)
29
+ end
30
+
8
31
  def self.build(request, response, response_body = nil, request_body = nil)
9
32
  message = []
10
33
  append_value message, 'request_method', request.request_method unless request.request_method.nil?
@@ -19,8 +42,6 @@ class HttpMessage
19
42
  return message
20
43
  end
21
44
 
22
- protected
23
-
24
45
  def self.append_request_headers(message, request)
25
46
  respond_to_env = request.respond_to?(:env)
26
47
  if respond_to_env || request.respond_to?(:headers)
@@ -74,12 +95,12 @@ class HttpMessage
74
95
  unless key.nil?
75
96
  unless value.nil?
76
97
  case value
77
- when Array
78
- message << [key, value.join]
79
- when String
80
- message << [key, value]
81
- else
82
- message << [key, value.to_s]
98
+ when Array
99
+ message << [key, value.join]
100
+ when String
101
+ message << [key, value]
102
+ else
103
+ message << [key, value.to_s]
83
104
  end
84
105
  end
85
106
  end
@@ -1,5 +1,5 @@
1
1
  # coding: utf-8
2
- # © 2016-2019 Resurface Labs Inc.
2
+ # © 2016-2020 Resurface Labs Inc.
3
3
 
4
4
  class HttpRequestImpl
5
5
 
@@ -1,5 +1,5 @@
1
1
  # coding: utf-8
2
- # © 2016-2019 Resurface Labs Inc.
2
+ # © 2016-2020 Resurface Labs Inc.
3
3
 
4
4
  class HttpResponseImpl
5
5
 
@@ -1,5 +1,5 @@
1
1
  # coding: utf-8
2
- # © 2016-2019 Resurface Labs Inc.
2
+ # © 2016-2020 Resurface Labs Inc.
3
3
 
4
4
  class HttpRule
5
5
 
@@ -1,37 +1,39 @@
1
1
  # coding: utf-8
2
- # © 2016-2019 Resurface Labs Inc.
2
+ # © 2016-2020 Resurface Labs Inc.
3
3
 
4
4
  class HttpRules
5
5
 
6
+ DEBUG_RULES = "allow_http_url\ncopy_session_field /.*/\n".freeze
7
+
8
+ STANDARD_RULES = %q(/request_header:cookie|response_header:set-cookie/ remove
9
+ /(request|response)_body|request_param/ replace /[a-zA-Z0-9.!#$%&’*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)/, /x@y.com/
10
+ /request_body|request_param|response_body/ replace /[0-9\.\-\/]{9,}/, /xyxy/
11
+ ).freeze
12
+
13
+ STRICT_RULES = %q(/request_url/ replace /([^\?;]+).*/, !\\\\1!
14
+ /request_body|response_body|request_param:.*|request_header:(?!user-agent).*|response_header:(?!(content-length)|(content-type)).*/ remove
15
+ ).freeze
16
+
17
+ @@default_rules = STRICT_RULES
18
+
19
+ def self.default_rules
20
+ @@default_rules
21
+ end
22
+
23
+ def self.default_rules=(val)
24
+ @@default_rules = val.gsub(/^\s*include default\s*$/, '')
25
+ end
26
+
6
27
  def self.debug_rules
7
- "allow_http_url\ncopy_session_field /.*/\n"
28
+ DEBUG_RULES
8
29
  end
9
30
 
10
31
  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
- )
32
+ STANDARD_RULES
15
33
  end
16
34
 
17
35
  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
36
+ STRICT_RULES
35
37
  end
36
38
 
37
39
  def self.parse_rule(r)
@@ -76,8 +78,6 @@ class HttpRules
76
78
  end
77
79
  end
78
80
 
79
- protected
80
-
81
81
  def self.parse_regex(r, regex)
82
82
  s = parse_string(r, regex)
83
83
  raise RuntimeError.new("Invalid regex (#{regex}) in rule: #{r}") if '*' == s || '+' == s || '?' == s
@@ -109,6 +109,154 @@ class HttpRules
109
109
  raise RuntimeError.new("Invalid expression (#{expr}) in rule: #{r}")
110
110
  end
111
111
 
112
+ def initialize(rules)
113
+ rules = HttpRules.default_rules if rules.nil?
114
+
115
+ # todo load rules from external files
116
+
117
+ # force default rules if necessary
118
+ rules = rules.gsub(/^\s*include default\s*$/, HttpRules.default_rules)
119
+ rules = HttpRules.default_rules unless rules.strip.length > 0
120
+
121
+ # expand rule inclues
122
+ rules = rules.gsub(/^\s*include debug\s*$/, DEBUG_RULES)
123
+ rules = rules.gsub(/^\s*include standard\s*$/, STANDARD_RULES)
124
+ rules = rules.gsub(/^\s*include strict\s*$/, STRICT_RULES)
125
+ @text = rules
126
+
127
+ # parse all rules
128
+ prs = []
129
+ rules.each_line do |rule|
130
+ parsed = HttpRules.parse_rule(rule)
131
+ prs << parsed unless parsed.nil?
132
+ end
133
+ @length = prs.length
134
+
135
+ # break out rules by verb
136
+ @allow_http_url = prs.select {|r| 'allow_http_url' == r.verb}.length > 0
137
+ @copy_session_field = prs.select {|r| 'copy_session_field' == r.verb}
138
+ @remove = prs.select {|r| 'remove' == r.verb}
139
+ @remove_if = prs.select {|r| 'remove_if' == r.verb}
140
+ @remove_if_found = prs.select {|r| 'remove_if_found' == r.verb}
141
+ @remove_unless = prs.select {|r| 'remove_unless' == r.verb}
142
+ @remove_unless_found = prs.select {|r| 'remove_unless_found' == r.verb}
143
+ @replace = prs.select {|r| 'replace' == r.verb}
144
+ @sample = prs.select {|r| 'sample' == r.verb}
145
+ @skip_compression = prs.select {|r| 'skip_compression' == r.verb}.length > 0
146
+ @skip_submission = prs.select {|r| 'skip_submission' == r.verb}.length > 0
147
+ @stop = prs.select {|r| 'stop' == r.verb}
148
+ @stop_if = prs.select {|r| 'stop_if' == r.verb}
149
+ @stop_if_found = prs.select {|r| 'stop_if_found' == r.verb}
150
+ @stop_unless = prs.select {|r| 'stop_unless' == r.verb}
151
+ @stop_unless_found = prs.select {|r| 'stop_unless_found' == r.verb}
152
+
153
+ # validate rules
154
+ raise RuntimeError.new('Multiple sample rules') if @sample.length > 1
155
+ end
156
+
157
+ def allow_http_url
158
+ @allow_http_url
159
+ end
160
+
161
+ def copy_session_field
162
+ @copy_session_field
163
+ end
164
+
165
+ def length
166
+ @length
167
+ end
168
+
169
+ def remove
170
+ @remove
171
+ end
172
+
173
+ def remove_if
174
+ @remove_if
175
+ end
176
+
177
+ def remove_if_found
178
+ @remove_if_found
179
+ end
180
+
181
+ def remove_unless
182
+ @remove_unless
183
+ end
184
+
185
+ def remove_unless_found
186
+ @remove_unless_found
187
+ end
188
+
189
+ def replace
190
+ @replace
191
+ end
192
+
193
+ def sample
194
+ @sample
195
+ end
196
+
197
+ def skip_compression
198
+ @skip_compression
199
+ end
200
+
201
+ def skip_submission
202
+ @skip_submission
203
+ end
204
+
205
+ def stop
206
+ @stop
207
+ end
208
+
209
+ def stop_if
210
+ @stop_if
211
+ end
212
+
213
+ def stop_if_found
214
+ @stop_if_found
215
+ end
216
+
217
+ def stop_unless
218
+ @stop_unless
219
+ end
220
+
221
+ def stop_unless_found
222
+ @stop_unless_found
223
+ end
224
+
225
+ def text
226
+ @text
227
+ end
228
+
229
+ def apply(details)
230
+ # stop rules come first
231
+ @stop.each {|r| details.each {|d| return nil if r.scope.match(d[0])}}
232
+ @stop_if_found.each {|r| details.each {|d| return nil if r.scope.match(d[0]) && r.param1.match(d[1])}}
233
+ @stop_if.each {|r| details.each {|d| return nil if r.scope.match(d[0]) && r.param1.match(d[1])}}
234
+ passed = 0
235
+ @stop_unless_found.each {|r| details.each {|d| passed += 1 if r.scope.match(d[0]) && r.param1.match(d[1])}}
236
+ return nil if passed != @stop_unless_found.length
237
+ passed = 0
238
+ @stop_unless.each {|r| details.each {|d| passed += 1 if r.scope.match(d[0]) && r.param1.match(d[1])}}
239
+ return nil if passed != @stop_unless.length
240
+
241
+ # do sampling if configured
242
+ return nil if !@sample[0].nil? && (rand * 100 >= @sample[0].param1)
243
+
244
+ # winnow sensitive details based on remove rules if configured
245
+ @remove.each {|r| details.delete_if {|d| r.scope.match(d[0])}}
246
+ @remove_unless_found.each {|r| details.delete_if {|d| r.scope.match(d[0]) && !r.param1.match(d[1])}}
247
+ @remove_if_found.each {|r| details.delete_if {|d| r.scope.match(d[0]) && r.param1.match(d[1])}}
248
+ @remove_unless.each {|r| details.delete_if {|d| r.scope.match(d[0]) && !r.param1.match(d[1])}}
249
+ @remove_if.each {|r| details.delete_if {|d| r.scope.match(d[0]) && r.param1.match(d[1])}}
250
+ return nil if details.empty?
251
+
252
+ # mask sensitive details based on replace rules if configured
253
+ @replace.each {|r| details.each {|d| d[1] = d[1].gsub(r.param1, r.param2) if r.scope.match(d[0])}}
254
+
255
+ # remove any details with empty values
256
+ details.delete_if {|d| '' == d[1]}
257
+ details.empty? ? nil : details
258
+ end
259
+
112
260
  REGEX_ALLOW_HTTP_URL = /^\s*allow_http_url\s*(#.*)?$/.freeze
113
261
  REGEX_BLANK_OR_COMMENT = /^\s*([#].*)*$/.freeze
114
262
  REGEX_COPY_SESSION_FIELD = /^\s*copy_session_field\s+([~!%|\/].+[~!%|\/])\s*(#.*)?$/.freeze
@@ -1,5 +1,5 @@
1
1
  # coding: utf-8
2
- # © 2016-2019 Resurface Labs Inc.
2
+ # © 2016-2020 Resurface Labs Inc.
3
3
 
4
4
  class UsageLoggers
5
5
 
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.10.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - RobDickinson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-06 00:00:00.000000000 Z
11
+ date: 2020-01-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler