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 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: