readme-metrics 0.2.0 → 1.0.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
  SHA256:
3
- metadata.gz: 66f7b071458adb5af02621626935bc6d9520bad8e246369a5f500f9037c3ea3b
4
- data.tar.gz: f3d313877ae15673722a1c749a5529e971ec17491a7eef639924cf3991419c49
3
+ metadata.gz: 17dad1b40b974d05ae3d57a664e6f95c393cbaf8e807071631c11800b2ee118c
4
+ data.tar.gz: 2d3595d321dea5bac6b5577180f608a2f9045e333592e5c33cca4c52a3e997ea
5
5
  SHA512:
6
- metadata.gz: be4ec7898257dbd67adf3e27070ea4cb1ee37b0d54d80de7ca2418356bfe3330a0fcdfa8ea3b16fed15afaa03a65ae0c58882f634385f37e76c275a2209f681d
7
- data.tar.gz: 76323ca324090a903898b7bd0ff818097272a6270466b4ed9eb58e06897c9becc6cc47264a0e64ee81e4a5a577aa7241ebd09f4d2bda9c3027e914b512b946e9
6
+ metadata.gz: a586f8ae1273269473451ea60bfeccb5e9fd01b6fa4ddacfd99b5918dfed24aa340a9e61aba6036a96b572c1eef50c4d2e1a9cd87162442197a02a4ff2456b2c
7
+ data.tar.gz: a7d894c039bdc676164f1dcd083650ae8ce026152baad7d83ea790a3ee4850e4de59246e922a641d6446943389c456557c1712b8b34c2085d4398e226c323c86
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- readme-metrics (0.1.0)
4
+ readme-metrics (0.2.0)
5
5
  httparty (~> 0.18)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -47,7 +47,7 @@ You may only specify either `reject_params` or `allow_only` keys, not both.
47
47
  ### Rails
48
48
 
49
49
  ```ruby
50
- # application.rb
50
+ # config/environments/development.rb or config/environments/production.rb
51
51
  require "readme/metrics"
52
52
 
53
53
  options = {
@@ -58,39 +58,51 @@ options = {
58
58
  }
59
59
 
60
60
  config.middleware.use Readme::Metrics, options do |env|
61
- current_user = env['warden'].authenticate(scope: :current_user)
61
+ current_user = env['warden'].authenticate
62
62
 
63
- {
64
- id: current_user.id
65
- label: current_user.full_name,
66
- email: current_user.email
67
- }
63
+ if current_user.present?
64
+ {
65
+ id: current_user.id,
66
+ label: current_user.name,
67
+ email: current_user.email
68
+ }
69
+ else
70
+ {
71
+ id: "guest",
72
+ label: "Guest User",
73
+ email: "guest@example.com"
74
+ }
75
+ end
68
76
  end
69
77
  ```
70
78
 
71
- ### Rack::Builder
79
+ ### Rack
72
80
 
73
81
  ```ruby
74
- Rack::Builder.new do |builder|
75
- options = {
76
- api_key: "YOUR_API_KEY",
77
- development: false,
78
- reject_params: ["not_included", "dont_send"]
79
- }
80
-
81
- builder.use Readme::Metrics, options do |env|
82
+ # config.ru
83
+ options = {
84
+ api_key: "YOUR_API_KEY",
85
+ development: false,
86
+ reject_params: ["not_included", "dont_send"]
87
+ }
88
+
89
+ use Readme::Metrics, options do |env|
82
90
  {
83
91
  id: "my_application_id"
84
92
  label: "My Application",
85
93
  email: "my.application@example.com"
86
94
  }
87
- end
88
- builder.run your_app
89
95
  end
96
+
97
+ run YourApp.new
90
98
  ```
91
99
 
92
- ## License
100
+ ### Sample Applications
93
101
 
94
- [View our license here](https://github.com/readmeio/metrics-sdks/tree/master/packages/ruby/LICENSE)
102
+ - [Rails](https://github.com/readmeio/metrics-sdk-rails-sample)
103
+ - [Rack](https://github.com/readmeio/metrics-sdk-racks-sample)
104
+ - [Sinatra](https://github.com/readmeio/metrics-sdk-sinatra-example)
95
105
 
106
+ ## License
96
107
 
108
+ [View our license here](https://github.com/readmeio/metrics-sdks/tree/master/packages/ruby/LICENSE)
@@ -0,0 +1,15 @@
1
+ module ContentTypeHelper
2
+ # Assumes the includer has a `content_type` method defined.
3
+
4
+ JSON_MIME_TYPES = [
5
+ "application/json",
6
+ "application/x-json",
7
+ "text/json",
8
+ "text/x-json",
9
+ "+json"
10
+ ]
11
+
12
+ def json?
13
+ JSON_MIME_TYPES.any? { |mime_type| content_type.include?(mime_type) }
14
+ end
15
+ end
@@ -1,7 +1,10 @@
1
1
  require "rack"
2
2
  require "rack/request"
3
+ require "content_type_helper"
3
4
 
4
5
  class HttpRequest
6
+ include ContentTypeHelper
7
+
5
8
  HTTP_NON_HEADERS = [
6
9
  Rack::HTTP_COOKIE,
7
10
  Rack::HTTP_VERSION,
@@ -55,6 +58,7 @@ class HttpRequest
55
58
  .select { |key, _| http_header?(key) }
56
59
  .to_h
57
60
  .transform_keys { |header| normalize_header_name(header) }
61
+ .merge unprefixed_headers
58
62
  end
59
63
 
60
64
  def body
@@ -85,4 +89,11 @@ class HttpRequest
85
89
  def normalize_header_name(header)
86
90
  header.delete_prefix("HTTP_").split("_").map(&:capitalize).join("-")
87
91
  end
92
+
93
+ # These special headers are explicitly _not_ prefixed with HTTP_ in the Rack
94
+ # env so we need to add them in manually
95
+ def unprefixed_headers
96
+ {"Content-Type" => @request.content_type,
97
+ "Content-Length" => @request.content_length}.compact
98
+ end
88
99
  end
@@ -0,0 +1,43 @@
1
+ require "rack"
2
+ require "rack/response"
3
+ require "content_type_helper"
4
+
5
+ class HttpResponse < SimpleDelegator
6
+ include ContentTypeHelper
7
+
8
+ def self.from_parts(status, headers, body)
9
+ new(Rack::Response.new(body, status, headers))
10
+ end
11
+
12
+ def body
13
+ if raw_body.respond_to?(:rewind)
14
+ raw_body.rewind
15
+ content = raw_body.each.reduce("", :+)
16
+ raw_body.rewind
17
+
18
+ content
19
+ else
20
+ raw_body.each.reduce("", :+)
21
+ end
22
+ end
23
+
24
+ def content_length
25
+ if empty_body_status?
26
+ 0
27
+ elsif !headers["Content-Length"]
28
+ body.bytesize
29
+ else
30
+ headers["Content-Length"].to_i
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def raw_body
37
+ __getobj__.body
38
+ end
39
+
40
+ def empty_body_status?
41
+ Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status.to_i)
42
+ end
43
+ end
@@ -13,21 +13,29 @@ class Filter
13
13
 
14
14
  class AllowOnly
15
15
  def initialize(filter_values)
16
- @filter_values = filter_values
16
+ @filter_values = filter_values.map(&:downcase)
17
17
  end
18
18
 
19
19
  def filter(hash)
20
- hash.select { |key, _value| @filter_values.include?(key) }
20
+ hash.select { |key, _value| @filter_values.include?(key.downcase) }
21
+ end
22
+
23
+ def pass_through?
24
+ false
21
25
  end
22
26
  end
23
27
 
24
28
  class RejectParams
25
29
  def initialize(filter_values)
26
- @filter_values = filter_values
30
+ @filter_values = filter_values.map(&:downcase)
27
31
  end
28
32
 
29
33
  def filter(hash)
30
- hash.reject { |key, _value| @filter_values.include?(key) }
34
+ hash.reject { |key, _value| @filter_values.include?(key.downcase) }
35
+ end
36
+
37
+ def pass_through?
38
+ false
31
39
  end
32
40
  end
33
41
 
@@ -35,6 +43,10 @@ class Filter
35
43
  def filter(hash)
36
44
  hash
37
45
  end
46
+
47
+ def pass_through?
48
+ true
49
+ end
38
50
  end
39
51
 
40
52
  class FilterArgsError < StandardError
@@ -46,9 +46,21 @@ module Readme
46
46
  end
47
47
 
48
48
  def request_body
49
+ if @filter.pass_through?
50
+ pass_through_body
51
+ else
52
+ # Only JSON allowed for non-pass-through situations. It will raise
53
+ # if the body can't be parsed as JSON, aborting the request.
54
+ json_body
55
+ end
56
+ end
57
+
58
+ def json_body
49
59
  parsed_body = JSON.parse(@request.body)
50
60
  Har::Collection.new(@filter, parsed_body).to_h.to_json
51
- rescue
61
+ end
62
+
63
+ def pass_through_body
52
64
  @request.body
53
65
  end
54
66
  end
@@ -4,8 +4,6 @@ require "readme/har/collection"
4
4
  module Readme
5
5
  module Har
6
6
  class ResponseSerializer
7
- JSON_MIME_TYPES = ["application/json", "application/x-json", "text/json", "text/x-json", "+json"]
8
-
9
7
  def initialize(request, response, filter)
10
8
  @request = request
11
9
  @response = response
@@ -29,9 +27,9 @@ module Readme
29
27
  private
30
28
 
31
29
  def content
32
- if response_body.nil?
30
+ if @response.body.empty?
33
31
  empty_content
34
- elsif content_type_is_json?
32
+ elsif @response.json?
35
33
  json_content
36
34
  else
37
35
  pass_through_content
@@ -43,37 +41,23 @@ module Readme
43
41
  end
44
42
 
45
43
  def json_content
46
- parsed_body = JSON.parse(response_body)
44
+ parsed_body = JSON.parse(@response.body)
47
45
 
48
- {mimeType: @response.content_type,
49
- size: @response.content_length,
50
- text: Har::Collection.new(@filter, parsed_body).to_h.to_json}
46
+ {
47
+ mimeType: @response.content_type,
48
+ size: @response.content_length,
49
+ text: Har::Collection.new(@filter, parsed_body).to_h.to_json
50
+ }
51
51
  rescue
52
52
  pass_through_content
53
53
  end
54
54
 
55
55
  def pass_through_content
56
- {mimeType: @response.content_type,
57
- size: @response.content_length,
58
- text: response_body}
59
- end
60
-
61
- def response_body
62
- if @response.body.nil?
63
- nil
64
- elsif @response.body.respond_to?(:rewind)
65
- @response.body.rewind
66
- body = @response.body.each.reduce(:+)
67
- @response.body.rewind
68
-
69
- body
70
- else
71
- @response.body.each.reduce(:+)
72
- end
73
- end
74
-
75
- def content_type_is_json?
76
- JSON_MIME_TYPES.include? @response.content_type
56
+ {
57
+ mimeType: @response.content_type,
58
+ size: @response.content_length,
59
+ text: @response.body
60
+ }
77
61
  end
78
62
  end
79
63
  end
@@ -5,6 +5,7 @@ require "readme/payload"
5
5
  require "readme/request_queue"
6
6
  require "readme/errors"
7
7
  require "http_request"
8
+ require "http_response"
8
9
  require "httparty"
9
10
  require "logger"
10
11
 
@@ -40,16 +41,17 @@ module Readme
40
41
  start_time = Time.now
41
42
  status, headers, body = @app.call(env)
42
43
  end_time = Time.now
43
- response = Rack::Response.new(body, status, headers)
44
44
 
45
45
  begin
46
+ response = HttpResponse.from_parts(status, headers, body)
46
47
  process_response(
47
48
  response: response,
48
49
  env: env,
49
50
  start_time: start_time,
50
51
  end_time: end_time
51
52
  )
52
- rescue
53
+ rescue => e
54
+ Readme::Metrics.logger.warn "The following error occured when trying to log to the ReadMe metrics API: #{e.message}. Request not logged."
53
55
  [status, headers, body]
54
56
  end
55
57
 
@@ -64,15 +66,33 @@ module Readme
64
66
  user_info = @get_user_info.call(env)
65
67
 
66
68
  if user_info_invalid?(user_info)
67
- Readme::Metrics.logger.error Errors.bad_block_message(user_info)
69
+ Readme::Metrics.logger.warn Errors.bad_block_message(user_info)
68
70
  elsif request.options?
69
71
  Readme::Metrics.logger.info "OPTIONS request omitted from ReadMe API logging"
72
+ elsif !can_filter? request, response
73
+ Readme::Metrics.logger.warn "Request or response body MIME type isn't supported for filtering. Omitting request from ReadMe API logging"
70
74
  else
71
75
  payload = Payload.new(har, user_info, development: @development)
72
76
  @@request_queue.push(payload.to_json)
73
77
  end
74
78
  end
75
79
 
80
+ def can_filter?(request, response)
81
+ @filter.pass_through? || can_parse_bodies?(request, response)
82
+ end
83
+
84
+ def can_parse_bodies?(request, response)
85
+ parseable_request?(request) && parseable_response?(response)
86
+ end
87
+
88
+ def parseable_response?(response)
89
+ response.body.empty? || response.json?
90
+ end
91
+
92
+ def parseable_request?(request)
93
+ request.body.empty? || request.json? || request.form_data?
94
+ end
95
+
76
96
  def validate_options(options)
77
97
  raise Errors::ConfigurationError, Errors::API_KEY_ERROR if options[:api_key].nil?
78
98
 
@@ -1,5 +1,5 @@
1
1
  module Readme
2
2
  class Metrics
3
- VERSION = "0.2.0"
3
+ VERSION = "1.0.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: readme-metrics
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ReadMe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-26 00:00:00.000000000 Z
11
+ date: 2020-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty
@@ -40,7 +40,9 @@ files:
40
40
  - Rakefile
41
41
  - bin/console
42
42
  - bin/setup
43
+ - lib/content_type_helper.rb
43
44
  - lib/http_request.rb
45
+ - lib/http_response.rb
44
46
  - lib/readme/errors.rb
45
47
  - lib/readme/filter.rb
46
48
  - lib/readme/har/collection.rb