resurfaceio-logger 1.8.4 → 1.9.0

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: a583801501358c56e7f049b70bb41f3695612f7a
4
- data.tar.gz: a3db5f5fad430ecadbcef92b75011ec62e478d54
3
+ metadata.gz: 95113134bbf15fba0ef898a514d24a24dd98da6e
4
+ data.tar.gz: 998377326921781e89c99e9905ebef0c6f932fc8
5
5
  SHA512:
6
- metadata.gz: a65d4f79c84c6c8b3768184c205e527e67704d963e154db0f4d1b1ff3d7ab9a8c3f8f09623a19d55f5eaae194830a3e1cfb0ceffd481cbe581f76050c82ce3ab
7
- data.tar.gz: 47a2f99ed81a16fbddd0248f743a63a124a67da57f8f2ba02363cf954f563bc3039b34f06f926685ad5fb3db527fa79eb901c5af13adbe1c405e5306c1ef73f3
6
+ metadata.gz: d64d21c53d46de1336ac33fc81f71e3ed1354709c8ec91fc239ed964c1e7d73fa7fc484b42223077836b6a2be5bdf9ad851d406c2c1cf72b82d8b2b00c544569
7
+ data.tar.gz: d7bac6e321c7d292e2190f15500e4df3e6d18dd0458bb221c565573fea7a19073501261d093c1dbef45f684bc2882d74720b981b84b68f6817e6653a0beb1c62
@@ -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/http_message_impl'
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 HttpMessageImpl < HttpMessageImpl
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
 
@@ -91,7 +91,7 @@ class BaseLogger
91
91
  end
92
92
 
93
93
  def submit(json)
94
- if @skip_submission || !enabled?
94
+ if json.nil? || @skip_submission || !enabled?
95
95
  true
96
96
  elsif @queue
97
97
  @queue << json
@@ -3,30 +3,121 @@
3
3
 
4
4
  require 'json'
5
5
  require 'resurfaceio/base_logger'
6
- require 'resurfaceio/http_message_impl'
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 format(request, response, response_body = nil, request_body = nil, now = nil)
21
- message = HttpMessageImpl.build(request, response, response_body, request_body)
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
- message = @logger.format(request, response)
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
- message = @logger.format(request, response)
24
- @logger.submit(message)
23
+ @logger.submit(@logger.format(request, response))
25
24
  end
26
25
  end
27
26
  end
@@ -3,7 +3,7 @@
3
3
 
4
4
  require 'json'
5
5
 
6
- class HttpMessageImpl
6
+ class HttpMessage
7
7
 
8
8
  def self.build(request, response, response_body = nil, request_body = nil)
9
9
  message = []
@@ -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.8.4
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: 2017-12-30 00:00:00.000000000 Z
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/http_message_impl.rb
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: