readme-metrics 0.1.0 → 1.1.1

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: 00a1c3d34d8c61c9fbcaacc3914ddb3e03837ce22bd3325dbabd30ba5b4c373e
4
- data.tar.gz: 34665a455715eaf423370cba89b31575dc87d53169c9bb8680f8b87a847e1704
3
+ metadata.gz: f22a117a4ef3e32682439eb3c6532766c442a32e067429857ed33548c2305ee1
4
+ data.tar.gz: 436f9b30983241c5ac5b1790e49c85268a7ef3afc3c07b208b13967f88ebfde6
5
5
  SHA512:
6
- metadata.gz: '05980a5c1bbdb0b23d438f18c8fcb27f931e3781e2e41ce938dff13611bfc6b70d8c61c141f644d4eeb3b1381019a3eff38e52da2adeb223cd1761ba8b6e53e7'
7
- data.tar.gz: 0a084c22580cc59868089773b85dec83ad295c1984ac616f1ad8a947a8914130f35c19c78bf05b8945188cddbf49882391a8375581c5f5f50d0af0531e9db2ea
6
+ metadata.gz: 432a22c8576ffb597876b7a899b18d1128c48226b27f4f0f30ae40f4855f50ce7586b169c0390257ab8da295a6f85aab618d9d3bbff24cc5f6bf00cf91dcaa91
7
+ data.tar.gz: abe8ecf0008727d3b05b984661db799ab5510ff201f3df5d8c05eb521e2f34231da44a915745dd9cea7ebd027c0c6fe48df312aa081cf75b93fc4bfd3049b5c6
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- readme-metrics (0.1.0)
4
+ readme-metrics (1.1.0)
5
5
  httparty (~> 0.18)
6
6
 
7
7
  GEM
@@ -21,7 +21,7 @@ GEM
21
21
  addressable (>= 2.4)
22
22
  mime-types (3.3.1)
23
23
  mime-types-data (~> 3.2015)
24
- mime-types-data (3.2020.0512)
24
+ mime-types-data (3.2021.0225)
25
25
  multi_xml (0.6.0)
26
26
  parallel (1.19.2)
27
27
  parser (2.7.1.4)
data/README.md CHANGED
@@ -1,7 +1,10 @@
1
- # readmeio
1
+ # readme-metrics
2
2
 
3
3
  Track your API metrics within ReadMe.
4
4
 
5
+ [![RubyGems](https://img.shields.io/gem/v/readme-metrics)](https://rubygems.org/gems/readme-metrics)
6
+ [![Build](https://github.com/readmeio/metrics-sdks/workflows/ruby/badge.svg)](https://github.com/readmeio/metrics-sdks)
7
+
5
8
  [![](https://d3vv6lp55qjaqc.cloudfront.net/items/1M3C3j0I0s0j3T362344/Untitled-2.png)](https://readme.io)
6
9
 
7
10
  ## Installation
@@ -24,8 +27,8 @@ current_user for a given request from the environment.
24
27
 
25
28
  ### Batching requests
26
29
 
27
- By default, the middleware will batch requests to the ReadMe API in groups of
28
- 10. For every 10 requests made to your application, the middleware will make a
30
+ By default, the middleware will batch requests to the ReadMe API in groups of 10.
31
+ For every 10 requests made to your application, the middleware will make a
29
32
  single request to ReadMe. If you wish to override this, provide a
30
33
  `buffer_length` option when configuring the middleware.
31
34
 
@@ -45,50 +48,62 @@ You may only specify either `reject_params` or `allow_only` keys, not both.
45
48
  ### Rails
46
49
 
47
50
  ```ruby
48
- # application.rb
51
+ # config/environments/development.rb or config/environments/production.rb
49
52
  require "readme/metrics"
50
53
 
51
54
  options = {
52
- api_key: "YOUR_API_KEY",
55
+ api_key: "<<apiKey>>",
53
56
  development: false,
54
57
  reject_params: ["not_included", "dont_send"],
55
58
  buffer_length: 5,
56
59
  }
57
60
 
58
61
  config.middleware.use Readme::Metrics, options do |env|
59
- current_user = env['warden'].authenticate(scope: :current_user)
62
+ current_user = env['warden'].authenticate
60
63
 
61
- {
62
- id: current_user.id
63
- label: current_user.full_name,
64
- email: current_user.email
65
- }
64
+ if current_user.present?
65
+ {
66
+ api_key: current_user.api_key, # Not the same as the ReadMe API Key
67
+ label: current_user.name,
68
+ email: current_user.email
69
+ }
70
+ else
71
+ {
72
+ api_key: "guest",
73
+ label: "Guest User",
74
+ email: "guest@example.com"
75
+ }
76
+ end
66
77
  end
67
78
  ```
68
79
 
69
- ### Rack::Builder
80
+ ### Rack
70
81
 
71
82
  ```ruby
72
- Rack::Builder.new do |builder|
73
- options = {
74
- api_key: "YOUR_API_KEY",
75
- development: false,
76
- reject_params: ["not_included", "dont_send"]
77
- }
78
-
79
- builder.use Readme::Metrics, options do |env|
83
+ # config.ru
84
+ options = {
85
+ api_key: "<<apiKey>>",
86
+ development: false,
87
+ reject_params: ["not_included", "dont_send"]
88
+ }
89
+
90
+ use Readme::Metrics, options do |env|
80
91
  {
81
- id: "my_application_id"
82
- label: "My Application",
83
- email: "my.application@example.com"
92
+ api_key: "owlbert_api_key"
93
+ label: "Owlbert",
94
+ email: "owlbert@example.com"
84
95
  }
85
- end
86
- builder.run your_app
87
96
  end
97
+
98
+ run YourApp.new
88
99
  ```
89
100
 
90
- ## License
101
+ ### Sample Applications
91
102
 
92
- [View our license here](https://github.com/readmeio/metrics-sdks/tree/master/packages/ruby/LICENSE)
103
+ - [Rails](https://github.com/readmeio/metrics-sdk-rails-sample)
104
+ - [Rack](https://github.com/readmeio/metrics-sdk-racks-sample)
105
+ - [Sinatra](https://github.com/readmeio/metrics-sdk-sinatra-example)
93
106
 
107
+ ## License
94
108
 
109
+ [View our license here](https://github.com/readmeio/metrics-sdks/tree/main/packages/ruby/LICENSE)
@@ -0,0 +1,17 @@
1
+ module Readme
2
+ module ContentTypeHelper
3
+ # Assumes the includer has a `content_type` method defined.
4
+
5
+ JSON_MIME_TYPES = [
6
+ "application/json",
7
+ "application/x-json",
8
+ "text/json",
9
+ "text/x-json",
10
+ "+json"
11
+ ]
12
+
13
+ def json?
14
+ JSON_MIME_TYPES.any? { |mime_type| content_type.include?(mime_type) }
15
+ end
16
+ end
17
+ end
data/lib/readme/errors.rb CHANGED
@@ -5,6 +5,10 @@ module Readme
5
5
  ALLOW_ONLY_ERROR = "The `allow_only` option must be an array of strings"
6
6
  BUFFER_LENGTH_ERROR = "The `buffer_length` must be an Integer"
7
7
  DEVELOPMENT_ERROR = "The `development` option must be a boolean"
8
+ LOGGER_ERROR = <<~MESSAGE
9
+ The `logger` option must be class that responds to the following messages:
10
+ :unkown, :fatal, :error, :warn, :info, :debug, :level
11
+ MESSAGE
8
12
 
9
13
  MISSING_BLOCK_ERROR = <<~MESSAGE
10
14
  Missing block argument. You must provide a block when configuring the
@@ -21,7 +25,7 @@ module Readme
21
25
  middleware.
22
26
 
23
27
  Expected a hash with the shape:
24
- { id: "unique_id", label: "Your user label", email: "Your user email" }
28
+ { api_key: "Your user api key", label: "Your user label", email: "Your user email" }
25
29
 
26
30
  Received value:
27
31
  #{result}
data/lib/readme/filter.rb CHANGED
@@ -1,46 +1,73 @@
1
- class Filter
2
- def self.for(reject: nil, allow_only: nil)
3
- if !reject.nil? && !allow_only.nil?
4
- raise FilterArgsError
5
- elsif !reject.nil?
6
- RejectParams.new(reject)
7
- elsif !allow_only.nil?
8
- AllowOnly.new(allow_only)
9
- else
10
- None.new
1
+ module Readme
2
+ class Filter
3
+ def self.for(reject: nil, allow_only: nil)
4
+ if !reject.nil? && !allow_only.nil?
5
+ raise FilterArgsError
6
+ elsif !reject.nil?
7
+ RejectParams.new(reject)
8
+ elsif !allow_only.nil?
9
+ AllowOnly.new(allow_only)
10
+ else
11
+ None.new
12
+ end
11
13
  end
12
- end
13
14
 
14
- class AllowOnly
15
- def initialize(filter_values)
16
- @filter_values = filter_values
15
+ def self.redact(rejected_params)
16
+ rejected_params.each_with_object({}) do |(k, v), hash|
17
+ # If it's a string then return the length of the redacted field
18
+ hash[k.to_str] = "[REDACTED#{v.is_a?(String) ? " #{v.length}" : ""}]"
19
+ end
17
20
  end
18
21
 
19
- def filter(hash)
20
- hash.select { |key, _value| @filter_values.include?(key) }
21
- end
22
- end
22
+ class AllowOnly
23
+ def initialize(filter_fields)
24
+ @allowed_fields = filter_fields
25
+ end
23
26
 
24
- class RejectParams
25
- def initialize(filter_values)
26
- @filter_values = filter_values
27
+ def filter(hash)
28
+ allowed_fields = @allowed_fields.map(&:downcase)
29
+ allowed_params, rejected_params = hash.partition { |key, _value| allowed_fields.include?(key.downcase) }.map(&:to_h)
30
+
31
+ allowed_params.merge(Filter.redact(rejected_params))
32
+ end
33
+
34
+ def pass_through?
35
+ false
36
+ end
27
37
  end
28
38
 
29
- def filter(hash)
30
- hash.reject { |key, _value| @filter_values.include?(key) }
39
+ class RejectParams
40
+ def initialize(filter_fields)
41
+ @rejected_fields = filter_fields
42
+ end
43
+
44
+ def filter(hash)
45
+ rejected_fields = @rejected_fields.map(&:downcase)
46
+ rejected_params, allowed_params = hash.partition { |key, _value| rejected_fields.include?(key.downcase) }.map(&:to_h)
47
+
48
+ allowed_params.merge(Filter.redact(rejected_params))
49
+ end
50
+
51
+ def pass_through?
52
+ false
53
+ end
31
54
  end
32
- end
33
55
 
34
- class None
35
- def filter(hash)
36
- hash
56
+ class None
57
+ def filter(hash)
58
+ hash
59
+ end
60
+
61
+ def pass_through?
62
+ true
63
+ end
37
64
  end
38
- end
39
65
 
40
- class FilterArgsError < StandardError
41
- def initialize
42
- msg = "Can only supply either reject_params or allow_only, not both."
43
- super(msg)
66
+ class FilterArgsError < StandardError
67
+ def initialize
68
+ msg = "Can only supply either reject_params or allow_only, not both."
69
+ super(msg)
70
+ end
44
71
  end
45
72
  end
46
73
  end
@@ -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
@@ -1,5 +1,4 @@
1
1
  require "rack"
2
- require "http_request"
3
2
  require "readme/metrics"
4
3
  require "readme/har/request_serializer"
5
4
  require "readme/har/response_serializer"
@@ -10,8 +9,8 @@ module Readme
10
9
  class Serializer
11
10
  HAR_VERSION = "1.2"
12
11
 
13
- def initialize(env, response, start_time, end_time, filter)
14
- @http_request = HttpRequest.new(env)
12
+ def initialize(request, response, start_time, end_time, filter)
13
+ @http_request = request
15
14
  @response = response
16
15
  @start_time = start_time
17
16
  @end_time = end_time
@@ -0,0 +1,101 @@
1
+ require "rack"
2
+ require "rack/request"
3
+ require_relative "content_type_helper"
4
+
5
+ module Readme
6
+ class HttpRequest
7
+ include ContentTypeHelper
8
+
9
+ HTTP_NON_HEADERS = [
10
+ Rack::HTTP_COOKIE,
11
+ Rack::HTTP_VERSION,
12
+ Rack::HTTP_HOST,
13
+ Rack::HTTP_PORT
14
+ ]
15
+
16
+ def initialize(env)
17
+ @request = Rack::Request.new(env)
18
+ end
19
+
20
+ def url
21
+ @request.url
22
+ end
23
+
24
+ def query_params
25
+ @request.GET
26
+ end
27
+
28
+ def cookies
29
+ @request.cookies
30
+ end
31
+
32
+ def http_version
33
+ @request.get_header(Rack::HTTP_VERSION)
34
+ end
35
+
36
+ def request_method
37
+ @request.request_method
38
+ end
39
+
40
+ def content_type
41
+ @request.content_type
42
+ end
43
+
44
+ def form_data?
45
+ @request.form_data?
46
+ end
47
+
48
+ def content_length
49
+ @request.content_length.to_i
50
+ end
51
+
52
+ def options?
53
+ @request.request_method == "OPTIONS"
54
+ end
55
+
56
+ def headers
57
+ @request
58
+ .each_header
59
+ .select { |key, _| http_header?(key) }
60
+ .to_h
61
+ .transform_keys { |header| normalize_header_name(header) }
62
+ .merge unprefixed_headers
63
+ end
64
+
65
+ def body
66
+ @request.body.rewind
67
+ content = @request.body.read
68
+ @request.body.rewind
69
+
70
+ content
71
+ end
72
+
73
+ def parsed_form_data
74
+ @request.POST
75
+ end
76
+
77
+ private
78
+
79
+ # "headers" in Rack::Request just means any key in the env. The HTTP headers
80
+ # are all the headers prefixed with `HTTP_` as per the spec:
81
+ # https://github.com/rack/rack/blob/master/SPEC.rdoc#the-environment-
82
+ # Other "headers" like version and host are prefixed with `HTTP_` by Rack but
83
+ # don't seem to be considered legit HTTP headers.
84
+ def http_header?(name)
85
+ name.start_with?("HTTP") && !HTTP_NON_HEADERS.include?(name)
86
+ end
87
+
88
+ # Headers like `Content-Type: application/json` come into rack like
89
+ # `"HTTP_CONTENT_TYPE" => "application/json"`.
90
+ def normalize_header_name(header)
91
+ header.delete_prefix("HTTP_").split("_").map(&:capitalize).join("-")
92
+ end
93
+
94
+ # These special headers are explicitly _not_ prefixed with HTTP_ in the Rack
95
+ # env so we need to add them in manually
96
+ def unprefixed_headers
97
+ {"Content-Type" => @request.content_type,
98
+ "Content-Length" => @request.content_length}.compact
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,45 @@
1
+ require "rack"
2
+ require "rack/response"
3
+ require_relative "content_type_helper"
4
+
5
+ module Readme
6
+ class HttpResponse < SimpleDelegator
7
+ include ContentTypeHelper
8
+
9
+ def self.from_parts(status, headers, body)
10
+ new(Rack::Response.new(body, status, headers))
11
+ end
12
+
13
+ def body
14
+ if raw_body.respond_to?(:rewind)
15
+ raw_body.rewind
16
+ content = raw_body.each.reduce("", :+)
17
+ raw_body.rewind
18
+
19
+ content
20
+ else
21
+ raw_body.each.reduce("", :+)
22
+ end
23
+ end
24
+
25
+ def content_length
26
+ if empty_body_status?
27
+ 0
28
+ elsif !headers["Content-Length"]
29
+ body.bytesize
30
+ else
31
+ headers["Content-Length"].to_i
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def raw_body
38
+ __getobj__.body
39
+ end
40
+
41
+ def empty_body_status?
42
+ Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status.to_i)
43
+ end
44
+ end
45
+ end
@@ -4,14 +4,22 @@ require "readme/filter"
4
4
  require "readme/payload"
5
5
  require "readme/request_queue"
6
6
  require "readme/errors"
7
+ require "readme/http_request"
8
+ require "readme/http_response"
7
9
  require "httparty"
10
+ require "logger"
8
11
 
9
12
  module Readme
10
13
  class Metrics
11
14
  SDK_NAME = "Readme.io Ruby SDK"
12
15
  DEFAULT_BUFFER_LENGTH = 10
13
16
  ENDPOINT = "https://metrics.readme.io/v1/request"
14
- USER_INFO_KEYS = [:id, :label, :email]
17
+ USER_INFO_KEYS = [:api_key, :label, :email]
18
+ USER_INFO_KEYS_DEPRECATED = [:id, :label, :email]
19
+
20
+ def self.logger
21
+ @@logger
22
+ end
15
23
 
16
24
  def initialize(app, options, &get_user_info)
17
25
  validate_options(options)
@@ -26,7 +34,8 @@ module Readme
26
34
  @get_user_info = get_user_info
27
35
 
28
36
  buffer_length = options[:buffer_length] || DEFAULT_BUFFER_LENGTH
29
- @@request_queue = Readme::RequestQueue.new(options[:api_key], buffer_length)
37
+ @@request_queue = options[:request_queue] || Readme::RequestQueue.new(options[:api_key], buffer_length)
38
+ @@logger = options[:logger] || Logger.new($stdout)
30
39
  end
31
40
 
32
41
  def call(env)
@@ -34,24 +43,56 @@ module Readme
34
43
  status, headers, body = @app.call(env)
35
44
  end_time = Time.now
36
45
 
37
- response = Rack::Response.new(body, status, headers)
46
+ begin
47
+ response = HttpResponse.from_parts(status, headers, body)
48
+ process_response(
49
+ response: response,
50
+ env: env,
51
+ start_time: start_time,
52
+ end_time: end_time
53
+ )
54
+ rescue => e
55
+ Readme::Metrics.logger.warn "The following error occured when trying to log to the ReadMe metrics API: #{e.message}. Request not logged."
56
+ [status, headers, body]
57
+ end
58
+
59
+ [status, headers, body]
60
+ end
38
61
 
39
- har = Har::Serializer.new(env, response, start_time, end_time, @filter)
62
+ private
40
63
 
64
+ def process_response(response:, env:, start_time:, end_time:)
65
+ request = HttpRequest.new(env)
66
+ har = Har::Serializer.new(request, response, start_time, end_time, @filter)
41
67
  user_info = @get_user_info.call(env)
42
68
 
43
- if user_info_invalid?(user_info)
44
- puts Errors.bad_block_message(user_info)
45
- return [status, headers, body]
69
+ if !user_info_valid?(user_info)
70
+ Readme::Metrics.logger.warn Errors.bad_block_message(user_info)
71
+ elsif request.options?
72
+ Readme::Metrics.logger.info "OPTIONS request omitted from ReadMe API logging"
73
+ elsif !can_filter? request, response
74
+ Readme::Metrics.logger.warn "Request or response body MIME type isn't supported for filtering. Omitting request from ReadMe API logging"
46
75
  else
47
76
  payload = Payload.new(har, user_info, development: @development)
48
77
  @@request_queue.push(payload.to_json)
49
78
  end
79
+ end
50
80
 
51
- [status, headers, body]
81
+ def can_filter?(request, response)
82
+ @filter.pass_through? || can_parse_bodies?(request, response)
52
83
  end
53
84
 
54
- private
85
+ def can_parse_bodies?(request, response)
86
+ parseable_request?(request) && parseable_response?(response)
87
+ end
88
+
89
+ def parseable_response?(response)
90
+ response.body.empty? || response.json?
91
+ end
92
+
93
+ def parseable_request?(request)
94
+ request.body.empty? || request.json? || request.form_data?
95
+ end
55
96
 
56
97
  def validate_options(options)
57
98
  raise Errors::ConfigurationError, Errors::API_KEY_ERROR if options[:api_key].nil?
@@ -71,16 +112,33 @@ module Readme
71
112
  if options[:development] && !is_a_boolean?(options[:development])
72
113
  raise Errors::ConfigurationError, Errors::DEVELOPMENT_ERROR
73
114
  end
115
+
116
+ if options[:logger] && has_logger_inferface?(options[:logger])
117
+ raise Errors::ConfigurationError, Errors::LOGGER_ERROR
118
+ end
119
+ end
120
+
121
+ def has_logger_inferface?(logger)
122
+ [
123
+ :unknown,
124
+ :fatal,
125
+ :error,
126
+ :warn,
127
+ :info,
128
+ :debug
129
+ ].any? { |message| !logger.respond_to? message }
74
130
  end
75
131
 
76
132
  def is_a_boolean?(arg)
77
133
  arg == true || arg == false
78
134
  end
79
135
 
80
- def user_info_invalid?(user_info)
81
- user_info.nil? ||
82
- user_info.values.any?(&:nil?) ||
83
- USER_INFO_KEYS.sort != user_info.keys.sort
136
+ def user_info_valid?(user_info)
137
+ sorted_user_info_keys = user_info.keys.sort
138
+ !user_info.nil? &&
139
+ !user_info.values.any?(&:nil?) &&
140
+ (sorted_user_info_keys === USER_INFO_KEYS.sort ||
141
+ sorted_user_info_keys === USER_INFO_KEYS_DEPRECATED.sort)
84
142
  end
85
143
  end
86
144
  end
@@ -1,5 +1,5 @@
1
1
  module Readme
2
2
  class Metrics
3
- VERSION = "0.1.0"
3
+ VERSION = "1.1.1"
4
4
  end
5
5
  end
@@ -2,6 +2,8 @@ module Readme
2
2
  class Payload
3
3
  def initialize(har, user_info, development:)
4
4
  @har = har
5
+ # swap api_key for id
6
+ user_info[:id] = user_info.delete :api_key unless user_info[:api_key].nil?
5
7
  @user_info = user_info
6
8
  @development = development
7
9
  end
@@ -2,19 +2,32 @@ require "readme/metrics"
2
2
 
3
3
  module Readme
4
4
  class RequestQueue
5
- attr_reader :queue
6
-
7
5
  def initialize(api_key, buffer_length)
8
6
  @queue = []
9
7
  @buffer_length = buffer_length
10
8
  @api_key = api_key
9
+ @lock = Mutex.new
11
10
  end
12
11
 
13
12
  def push(request)
14
- @queue << request
13
+ @lock.synchronize do
14
+ @queue << request
15
+
16
+ if ready_to_send?
17
+ payloads = @queue.slice!(0, @buffer_length)
18
+ send_payloads(payloads)
19
+ end
20
+ end
21
+ end
22
+
23
+ def length
24
+ @queue.length
25
+ end
26
+
27
+ private
15
28
 
16
- if ready_to_send?
17
- payloads = @queue.slice!(0, @buffer_length)
29
+ def send_payloads(payloads)
30
+ Thread.new do
18
31
  HTTParty.post(
19
32
  Readme::Metrics::ENDPOINT,
20
33
  basic_auth: {username: @api_key, password: ""},
@@ -24,10 +37,8 @@ module Readme
24
37
  end
25
38
  end
26
39
 
27
- private
28
-
29
40
  def ready_to_send?
30
- @queue.length >= @buffer_length
41
+ length >= @buffer_length
31
42
  end
32
43
 
33
44
  def to_json(payloads)
@@ -13,8 +13,8 @@ Gem::Specification.new do |spec|
13
13
  spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
14
14
 
15
15
  spec.metadata["homepage_uri"] = spec.homepage
16
- spec.metadata["source_code_uri"] = "https://github.com/readmeio/metrics-sdks/blob/master/packages/ruby"
17
- spec.metadata["changelog_uri"] = "https://github.com/readmeio/metrics-sdks/blob/master/packages/ruby/CHANGELOG.md"
16
+ spec.metadata["source_code_uri"] = "https://github.com/readmeio/metrics-sdks/blob/main/packages/ruby"
17
+ spec.metadata["changelog_uri"] = "https://github.com/readmeio/metrics-sdks/blob/main/packages/ruby/CHANGELOG.md"
18
18
 
19
19
  # Specify which files should be added to the gem when it is released.
20
20
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
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.1.0
4
+ version: 1.1.1
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-20 00:00:00.000000000 Z
11
+ date: 2021-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty
@@ -40,13 +40,15 @@ files:
40
40
  - Rakefile
41
41
  - bin/console
42
42
  - bin/setup
43
- - lib/http_request.rb
43
+ - lib/readme/content_type_helper.rb
44
44
  - lib/readme/errors.rb
45
45
  - lib/readme/filter.rb
46
46
  - lib/readme/har/collection.rb
47
47
  - lib/readme/har/request_serializer.rb
48
48
  - lib/readme/har/response_serializer.rb
49
49
  - lib/readme/har/serializer.rb
50
+ - lib/readme/http_request.rb
51
+ - lib/readme/http_response.rb
50
52
  - lib/readme/metrics.rb
51
53
  - lib/readme/metrics/version.rb
52
54
  - lib/readme/payload.rb
@@ -57,8 +59,8 @@ licenses:
57
59
  - ISC
58
60
  metadata:
59
61
  homepage_uri: https://docs.readme.com/metrics/docs/getting-started-with-api-metrics
60
- source_code_uri: https://github.com/readmeio/metrics-sdks/blob/master/packages/ruby
61
- changelog_uri: https://github.com/readmeio/metrics-sdks/blob/master/packages/ruby/CHANGELOG.md
62
+ source_code_uri: https://github.com/readmeio/metrics-sdks/blob/main/packages/ruby
63
+ changelog_uri: https://github.com/readmeio/metrics-sdks/blob/main/packages/ruby/CHANGELOG.md
62
64
  post_install_message:
63
65
  rdoc_options: []
64
66
  require_paths:
@@ -74,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
74
76
  - !ruby/object:Gem::Version
75
77
  version: '0'
76
78
  requirements: []
77
- rubygems_version: 3.1.2
79
+ rubygems_version: 3.1.4
78
80
  signing_key:
79
81
  specification_version: 4
80
82
  summary: SDK for Readme's metrics API
data/lib/http_request.rb DELETED
@@ -1,84 +0,0 @@
1
- require "rack"
2
- require "rack/request"
3
-
4
- class HttpRequest
5
- HTTP_NON_HEADERS = [
6
- Rack::HTTP_COOKIE,
7
- Rack::HTTP_VERSION,
8
- Rack::HTTP_HOST,
9
- Rack::HTTP_PORT
10
- ]
11
-
12
- def initialize(env)
13
- @request = Rack::Request.new(env)
14
- end
15
-
16
- def url
17
- @request.url
18
- end
19
-
20
- def query_params
21
- @request.GET
22
- end
23
-
24
- def cookies
25
- @request.cookies
26
- end
27
-
28
- def http_version
29
- @request.get_header(Rack::HTTP_VERSION)
30
- end
31
-
32
- def request_method
33
- @request.request_method
34
- end
35
-
36
- def content_type
37
- @request.content_type
38
- end
39
-
40
- def form_data?
41
- @request.form_data?
42
- end
43
-
44
- def content_length
45
- @request.content_length.to_i
46
- end
47
-
48
- def headers
49
- @request
50
- .each_header
51
- .select { |key, _| http_header?(key) }
52
- .to_h
53
- .transform_keys { |header| normalize_header_name(header) }
54
- end
55
-
56
- def body
57
- @request.body.rewind
58
- content = @request.body.read
59
- @request.body.rewind
60
-
61
- content
62
- end
63
-
64
- def parsed_form_data
65
- @request.POST
66
- end
67
-
68
- private
69
-
70
- # "headers" in Rack::Request just means any key in the env. The HTTP headers
71
- # are all the headers prefixed with `HTTP_` as per the spec:
72
- # https://github.com/rack/rack/blob/master/SPEC.rdoc#the-environment-
73
- # Other "headers" like version and host are prefixed with `HTTP_` by Rack but
74
- # don't seem to be considered legit HTTP headers.
75
- def http_header?(name)
76
- name.start_with?("HTTP") && !HTTP_NON_HEADERS.include?(name)
77
- end
78
-
79
- # Headers like `Content-Type: application/json` come into rack like
80
- # `"HTTP_CONTENT_TYPE" => "application/json"`.
81
- def normalize_header_name(header)
82
- header.delete_prefix("HTTP_").split("_").map(&:capitalize).join("-")
83
- end
84
- end