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 +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +42 -27
- data/lib/readme/content_type_helper.rb +17 -0
- data/lib/readme/errors.rb +5 -1
- data/lib/readme/filter.rb +59 -32
- data/lib/readme/har/request_serializer.rb +13 -1
- data/lib/readme/har/response_serializer.rb +13 -29
- data/lib/readme/har/serializer.rb +2 -3
- data/lib/readme/http_request.rb +101 -0
- data/lib/readme/http_response.rb +45 -0
- data/lib/readme/metrics.rb +71 -13
- data/lib/readme/metrics/version.rb +1 -1
- data/lib/readme/payload.rb +2 -0
- data/lib/readme/request_queue.rb +19 -8
- data/readme-metrics.gemspec +2 -2
- metadata +8 -6
- data/lib/http_request.rb +0 -84
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f22a117a4ef3e32682439eb3c6532766c442a32e067429857ed33548c2305ee1
|
4
|
+
data.tar.gz: 436f9b30983241c5ac5b1790e49c85268a7ef3afc3c07b208b13967f88ebfde6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 (
|
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.
|
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
|
-
#
|
1
|
+
# readme-metrics
|
2
2
|
|
3
3
|
Track your API metrics within ReadMe.
|
4
4
|
|
5
|
+
[](https://rubygems.org/gems/readme-metrics)
|
6
|
+
[](https://github.com/readmeio/metrics-sdks)
|
7
|
+
|
5
8
|
[](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
|
-
|
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
|
-
#
|
51
|
+
# config/environments/development.rb or config/environments/production.rb
|
49
52
|
require "readme/metrics"
|
50
53
|
|
51
54
|
options = {
|
52
|
-
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
|
62
|
+
current_user = env['warden'].authenticate
|
60
63
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
80
|
+
### Rack
|
70
81
|
|
71
82
|
```ruby
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
82
|
-
label: "
|
83
|
-
email: "
|
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
|
-
|
101
|
+
### Sample Applications
|
91
102
|
|
92
|
-
[
|
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
|
-
{
|
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
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
22
|
+
class AllowOnly
|
23
|
+
def initialize(filter_fields)
|
24
|
+
@allowed_fields = filter_fields
|
25
|
+
end
|
23
26
|
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
30
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
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
|
30
|
+
if @response.body.empty?
|
33
31
|
empty_content
|
34
|
-
elsif
|
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(
|
44
|
+
parsed_body = JSON.parse(@response.body)
|
47
45
|
|
48
|
-
{
|
49
|
-
|
50
|
-
|
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
|
-
{
|
57
|
-
|
58
|
-
|
59
|
-
|
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(
|
14
|
-
@http_request =
|
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
|
data/lib/readme/metrics.rb
CHANGED
@@ -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 = [:
|
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
|
-
|
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
|
-
|
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
|
44
|
-
|
45
|
-
|
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
|
-
|
81
|
+
def can_filter?(request, response)
|
82
|
+
@filter.pass_through? || can_parse_bodies?(request, response)
|
52
83
|
end
|
53
84
|
|
54
|
-
|
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
|
81
|
-
user_info.
|
82
|
-
|
83
|
-
|
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
|
data/lib/readme/payload.rb
CHANGED
data/lib/readme/request_queue.rb
CHANGED
@@ -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
|
-
@
|
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
|
-
|
17
|
-
|
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
|
-
|
41
|
+
length >= @buffer_length
|
31
42
|
end
|
32
43
|
|
33
44
|
def to_json(payloads)
|
data/readme-metrics.gemspec
CHANGED
@@ -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/
|
17
|
-
spec.metadata["changelog_uri"] = "https://github.com/readmeio/metrics-sdks/blob/
|
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:
|
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:
|
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/
|
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/
|
61
|
-
changelog_uri: https://github.com/readmeio/metrics-sdks/blob/
|
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.
|
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
|