readme-metrics 0.1.0 → 1.1.1

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