readme-metrics 1.1.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +67 -0
- data/Gemfile +10 -7
- data/Gemfile.lock +21 -13
- data/LICENSE +1 -1
- data/Makefile +14 -0
- data/README.md +21 -100
- data/Rakefile +2 -2
- data/bin/console +3 -3
- data/lib/readme/content_type_helper.rb +6 -6
- data/lib/readme/errors.rb +7 -5
- data/lib/readme/filter.rb +2 -2
- data/lib/readme/har/collection.rb +1 -1
- data/lib/readme/har/request_serializer.rb +36 -9
- data/lib/readme/har/response_serializer.rb +3 -3
- data/lib/readme/har/serializer.rb +12 -9
- data/lib/readme/http_request.rb +11 -9
- data/lib/readme/http_response.rb +7 -7
- data/lib/readme/metrics/version.rb +3 -1
- data/lib/readme/metrics.rb +34 -33
- data/lib/readme/payload.rb +15 -6
- data/lib/readme/request_queue.rb +4 -4
- data/readme-metrics.gemspec +15 -13
- metadata +24 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: edf64efc0c1f3a7ba72f9f6d30aaebbd1caf64774258fe7dc69e343751358adb
|
4
|
+
data.tar.gz: 36dd67292da34691382b5e3d9f7234275c9d14d4b133f8067ed22c2c0c861a02
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20c0d88a3e19f49a742ad52555b22f4cfa1d6fc34eeea60dbeb3410f97050d546ed9caa4d00ba5c518822296d9660a6e24c84c7232165724251cafd68dadab53
|
7
|
+
data.tar.gz: c29cb1edddfd487996d345be984f402353a1940c4626aad107b4df33537800de0cab8ba41cf294d9a7bfaae5f221cf266012bea8d925ac8794e717b7059c3238
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require:
|
2
|
+
- rubocop-performance
|
3
|
+
- rubocop-rspec
|
4
|
+
|
5
|
+
AllCops:
|
6
|
+
NewCops: enable
|
7
|
+
TargetRubyVersion: 2.7
|
8
|
+
|
9
|
+
# https://docs.rubocop.org/rubocop/cops_layout.html
|
10
|
+
Layout/LineLength:
|
11
|
+
Max: 150
|
12
|
+
|
13
|
+
# https://docs.rubocop.org/rubocop/cops_metrics.html
|
14
|
+
Metrics/AbcSize:
|
15
|
+
Enabled: false
|
16
|
+
Metrics/CyclomaticComplexity:
|
17
|
+
Max: 20
|
18
|
+
Metrics/BlockLength:
|
19
|
+
Exclude:
|
20
|
+
- spec/readme/*.rb
|
21
|
+
- spec/readme/har/*.rb
|
22
|
+
Metrics/ClassLength:
|
23
|
+
Enabled: false
|
24
|
+
Metrics/MethodLength:
|
25
|
+
Enabled: false
|
26
|
+
Metrics/PerceivedComplexity:
|
27
|
+
Max: 20
|
28
|
+
|
29
|
+
# https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec
|
30
|
+
RSpec/AnyInstance:
|
31
|
+
Enabled: false
|
32
|
+
RSpec/ExampleLength:
|
33
|
+
Enabled: false
|
34
|
+
RSpec/LeakyConstantDeclaration:
|
35
|
+
Exclude:
|
36
|
+
- spec/readme/*.rb
|
37
|
+
RSpec/MultipleExpectations:
|
38
|
+
Enabled: false
|
39
|
+
RSpec/VerifiedDoubles:
|
40
|
+
Enabled: false
|
41
|
+
|
42
|
+
# https://docs.rubocop.org/rubocop/cops_style.html
|
43
|
+
Style/BlockDelimiters:
|
44
|
+
Exclude:
|
45
|
+
- spec/**/*
|
46
|
+
Style/ClassVars:
|
47
|
+
Exclude:
|
48
|
+
- 'lib/readme/metrics.rb'
|
49
|
+
Style/Documentation:
|
50
|
+
Enabled: false
|
51
|
+
Style/ExpandPathArguments:
|
52
|
+
Enabled: false
|
53
|
+
Style/FrozenStringLiteralComment:
|
54
|
+
Enabled: false
|
55
|
+
Style/GuardClause:
|
56
|
+
Exclude:
|
57
|
+
- 'lib/readme/filter.rb'
|
58
|
+
- 'lib/readme/metrics.rb'
|
59
|
+
Style/IfUnlessModifier:
|
60
|
+
Exclude:
|
61
|
+
- 'lib/readme/metrics.rb'
|
62
|
+
Style/RescueStandardError:
|
63
|
+
# We should ideally have this rule enabled but I'm paranoid that we don't have full documentation
|
64
|
+
# on all types of errors that might be thrown in the couple places this rule is being suppressed
|
65
|
+
# and by only catching `StandardError` we'll miss out on something else and potentially take down
|
66
|
+
# someones integration.
|
67
|
+
Enabled: false
|
data/Gemfile
CHANGED
@@ -1,13 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
source
|
3
|
+
source 'https://rubygems.org'
|
4
4
|
|
5
5
|
# Specify your gem's dependencies in readme-metrics.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
|
-
gem
|
9
|
-
gem
|
10
|
-
gem
|
11
|
-
gem
|
12
|
-
gem
|
13
|
-
gem
|
8
|
+
gem 'json-schema'
|
9
|
+
gem 'rack-test'
|
10
|
+
gem 'rake', '~> 12.0'
|
11
|
+
gem 'rspec', '~> 3.0'
|
12
|
+
gem 'rubocop', require: false
|
13
|
+
gem 'rubocop-performance', require: false
|
14
|
+
gem 'rubocop-rspec', require: false
|
15
|
+
gem 'uuid'
|
16
|
+
gem 'webmock'
|
data/Gemfile.lock
CHANGED
@@ -1,33 +1,36 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
readme-metrics (
|
4
|
+
readme-metrics (2.0.1)
|
5
5
|
httparty (~> 0.18)
|
6
|
+
uuid (~> 2.3.8)
|
6
7
|
|
7
8
|
GEM
|
8
9
|
remote: https://rubygems.org/
|
9
10
|
specs:
|
10
|
-
addressable (2.
|
11
|
+
addressable (2.8.0)
|
11
12
|
public_suffix (>= 2.0.2, < 5.0)
|
12
13
|
ast (2.4.1)
|
13
14
|
crack (0.4.3)
|
14
15
|
safe_yaml (~> 1.0.0)
|
15
16
|
diff-lcs (1.4.4)
|
16
17
|
hashdiff (1.0.1)
|
17
|
-
httparty (0.
|
18
|
+
httparty (0.20.0)
|
18
19
|
mime-types (~> 3.0)
|
19
20
|
multi_xml (>= 0.5.2)
|
20
21
|
json-schema (2.8.1)
|
21
22
|
addressable (>= 2.4)
|
22
|
-
|
23
|
+
macaddr (1.7.2)
|
24
|
+
systemu (~> 2.6.5)
|
25
|
+
mime-types (3.4.1)
|
23
26
|
mime-types-data (~> 3.2015)
|
24
|
-
mime-types-data (3.
|
27
|
+
mime-types-data (3.2022.0105)
|
25
28
|
multi_xml (0.6.0)
|
26
29
|
parallel (1.19.2)
|
27
30
|
parser (2.7.1.4)
|
28
31
|
ast (~> 2.4.1)
|
29
|
-
public_suffix (4.0.
|
30
|
-
rack (2.2.3)
|
32
|
+
public_suffix (4.0.6)
|
33
|
+
rack (2.2.3.1)
|
31
34
|
rack-test (1.1.0)
|
32
35
|
rack (>= 1.0, < 3)
|
33
36
|
rainbow (3.0.0)
|
@@ -58,14 +61,16 @@ GEM
|
|
58
61
|
unicode-display_width (>= 1.4.0, < 2.0)
|
59
62
|
rubocop-ast (0.3.0)
|
60
63
|
parser (>= 2.7.1.4)
|
61
|
-
rubocop-performance (1.
|
62
|
-
rubocop (>= 0.
|
64
|
+
rubocop-performance (1.7.1)
|
65
|
+
rubocop (>= 0.82.0)
|
66
|
+
rubocop-rspec (1.41.0)
|
67
|
+
rubocop (>= 0.68.1)
|
63
68
|
ruby-progressbar (1.10.1)
|
64
69
|
safe_yaml (1.0.5)
|
65
|
-
|
66
|
-
rubocop (~> 0.85.0)
|
67
|
-
rubocop-performance (~> 1.6.0)
|
70
|
+
systemu (2.6.5)
|
68
71
|
unicode-display_width (1.7.0)
|
72
|
+
uuid (2.3.9)
|
73
|
+
macaddr (~> 1.0)
|
69
74
|
webmock (3.8.3)
|
70
75
|
addressable (>= 2.3.6)
|
71
76
|
crack (>= 0.3.2)
|
@@ -80,7 +85,10 @@ DEPENDENCIES
|
|
80
85
|
rake (~> 12.0)
|
81
86
|
readme-metrics!
|
82
87
|
rspec (~> 3.0)
|
83
|
-
|
88
|
+
rubocop
|
89
|
+
rubocop-performance
|
90
|
+
rubocop-rspec
|
91
|
+
uuid
|
84
92
|
webmock
|
85
93
|
|
86
94
|
BUNDLED WITH
|
data/LICENSE
CHANGED
data/Makefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
install: ## Install all dependencies
|
2
|
+
bundle
|
3
|
+
|
4
|
+
lint: ## Run code standard checks
|
5
|
+
bundle exec rubocop
|
6
|
+
|
7
|
+
lint-fix: ## Attempt to automatically fix any code standard violations
|
8
|
+
bundle exec rubocop --auto-correct
|
9
|
+
|
10
|
+
test: ## Run unit tests
|
11
|
+
rake spec
|
12
|
+
|
13
|
+
help: ## Show this help
|
14
|
+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
data/README.md
CHANGED
@@ -1,109 +1,30 @@
|
|
1
|
-
#
|
1
|
+
# ReadMe Metrics
|
2
2
|
|
3
|
-
|
3
|
+
<p align="center">
|
4
|
+
<img src="https://user-images.githubusercontent.com/33762/182927634-2aebeb46-c215-4ac3-9e98-61f931e33583.png" />
|
5
|
+
</p>
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
+
<p align="center">
|
8
|
+
Track usage of your API and troubleshoot issues faster.
|
9
|
+
</p>
|
7
10
|
|
8
|
-
|
11
|
+
<p align="center">
|
12
|
+
<a href="https://rubygems.org/gems/readme-metrics"><img src="https://img.shields.io/gem/v/readme-metrics.svg?style=for-the-badge" alt="Latest release"></a>
|
13
|
+
<a href="https://github.com/readmeio/metrics-sdks"><img src="https://img.shields.io/github/workflow/status/readmeio/metrics-sdks/ruby.svg?style=for-the-badge" alt="Build status"></a>
|
14
|
+
</p>
|
9
15
|
|
10
|
-
|
16
|
+
With [ReadMe's Metrics API](https://readme.com/metrics) your team can get deep insights into your API's usage. If you're a developer, it takes a few small steps to send your API logs to [ReadMe](http://readme.com). Here's an overview of how the integration works:
|
11
17
|
|
12
|
-
|
18
|
+
- You add the ReadMe middleware to your Rails application.
|
19
|
+
- The middleware sends to ReadMe the request and response objects that your Express server generates each time a user makes a request to your API. The entire objects are sent, unless you allow or deny keys.
|
20
|
+
- ReadMe extracts information to display in Metrics, such as which endpoint is being called, response code, and error messages. It also identifies the customer who called your API, using whichever keys in the middleware you call out as containing relevant customer info.
|
13
21
|
|
14
|
-
|
15
|
-
|
16
|
-
## Usage
|
17
|
-
|
18
|
-
`Readme::Metrics` is a Rack middleware and is compatible with all Rack-based
|
19
|
-
apps, including Rails.
|
20
|
-
|
21
|
-
When configuring the middleware, you must provide a block to tell the
|
22
|
-
middleware how to get values for the current user. These may be values taken
|
23
|
-
from the environment, or you may hardcode them.
|
24
|
-
|
25
|
-
If you're using Warden-based authentication like Devise, you may fetch the
|
26
|
-
current_user for a given request from the environment.
|
27
|
-
|
28
|
-
### Batching requests
|
29
|
-
|
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
|
32
|
-
single request to ReadMe. If you wish to override this, provide a
|
33
|
-
`buffer_length` option when configuring the middleware.
|
34
|
-
|
35
|
-
### Sensitive Data
|
36
|
-
|
37
|
-
If you have sensitive data you'd like to prevent from being sent to the Metrics
|
38
|
-
API via headers, query params or payload bodies, you can specify a list of keys
|
39
|
-
to filter via the `reject_params` option. Key-value pairs matching these keys
|
40
|
-
will not be included in the request to the Metrics API.
|
41
|
-
|
42
|
-
You are also able to specify a set of `allow_only` which should only be sent through.
|
43
|
-
Any header or body values not matching these keys will be filtered out and not
|
44
|
-
send to the API.
|
45
|
-
|
46
|
-
You may only specify either `reject_params` or `allow_only` keys, not both.
|
47
|
-
|
48
|
-
### Rails
|
49
|
-
|
50
|
-
```ruby
|
51
|
-
# config/environments/development.rb or config/environments/production.rb
|
52
|
-
require "readme/metrics"
|
53
|
-
|
54
|
-
options = {
|
55
|
-
api_key: "<<apiKey>>",
|
56
|
-
development: false,
|
57
|
-
reject_params: ["not_included", "dont_send"],
|
58
|
-
buffer_length: 5,
|
59
|
-
}
|
60
|
-
|
61
|
-
config.middleware.use Readme::Metrics, options do |env|
|
62
|
-
current_user = env['warden'].authenticate
|
63
|
-
|
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
|
77
|
-
end
|
22
|
+
```bash
|
23
|
+
gem "readme-metrics"
|
78
24
|
```
|
79
25
|
|
80
|
-
|
81
|
-
|
82
|
-
```ruby
|
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|
|
91
|
-
{
|
92
|
-
api_key: "owlbert_api_key"
|
93
|
-
label: "Owlbert",
|
94
|
-
email: "owlbert@example.com"
|
95
|
-
}
|
96
|
-
end
|
97
|
-
|
98
|
-
run YourApp.new
|
99
|
-
```
|
100
|
-
|
101
|
-
### Sample Applications
|
102
|
-
|
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)
|
106
|
-
|
107
|
-
## License
|
26
|
+
**For more information on setup, check out our [integration documentation](https://docs.readme.com/docs/ruby-api-metrics-set-up).**
|
108
27
|
|
109
|
-
|
28
|
+
> 🚧 Any Issues?
|
29
|
+
>
|
30
|
+
> Integrations can be tricky! [Contact support](https://docs.readme.com/guides/docs/contact-support) if you have any questions/issues.
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'readme/metrics'
|
5
5
|
|
6
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +10,5 @@ require "readme/metrics"
|
|
10
10
|
# require "pry"
|
11
11
|
# Pry.start
|
12
12
|
|
13
|
-
require
|
13
|
+
require 'irb'
|
14
14
|
IRB.start(__FILE__)
|
@@ -3,12 +3,12 @@ module Readme
|
|
3
3
|
# Assumes the includer has a `content_type` method defined.
|
4
4
|
|
5
5
|
JSON_MIME_TYPES = [
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
]
|
6
|
+
'application/json',
|
7
|
+
'application/x-json',
|
8
|
+
'text/json',
|
9
|
+
'text/x-json',
|
10
|
+
'+json'
|
11
|
+
].freeze
|
12
12
|
|
13
13
|
def json?
|
14
14
|
JSON_MIME_TYPES.any? { |mime_type| content_type.include?(mime_type) }
|
data/lib/readme/errors.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
1
3
|
module Readme
|
2
4
|
class Errors
|
3
|
-
API_KEY_ERROR =
|
4
|
-
REJECT_PARAMS_ERROR =
|
5
|
-
ALLOW_ONLY_ERROR =
|
6
|
-
BUFFER_LENGTH_ERROR =
|
7
|
-
DEVELOPMENT_ERROR =
|
5
|
+
API_KEY_ERROR = 'Missing API Key'
|
6
|
+
REJECT_PARAMS_ERROR = 'The `reject_params` option must be an array of strings'
|
7
|
+
ALLOW_ONLY_ERROR = 'The `allow_only` option must be an array of strings'
|
8
|
+
BUFFER_LENGTH_ERROR = 'The `buffer_length` must be an Integer'
|
9
|
+
DEVELOPMENT_ERROR = 'The `development` option must be a boolean'
|
8
10
|
LOGGER_ERROR = <<~MESSAGE
|
9
11
|
The `logger` option must be class that responds to the following messages:
|
10
12
|
:unkown, :fatal, :error, :warn, :info, :debug, :level
|
data/lib/readme/filter.rb
CHANGED
@@ -15,7 +15,7 @@ module Readme
|
|
15
15
|
def self.redact(rejected_params)
|
16
16
|
rejected_params.each_with_object({}) do |(k, v), hash|
|
17
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}" :
|
18
|
+
hash[k.to_str] = "[REDACTED#{v.is_a?(String) ? " #{v.length}" : ''}]"
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -65,7 +65,7 @@ module Readme
|
|
65
65
|
|
66
66
|
class FilterArgsError < StandardError
|
67
67
|
def initialize
|
68
|
-
msg =
|
68
|
+
msg = 'Can only supply either reject_params or allow_only, not both.'
|
69
69
|
super(msg)
|
70
70
|
end
|
71
71
|
end
|
@@ -1,10 +1,11 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'cgi'
|
2
|
+
require 'readme/har/collection'
|
3
|
+
require 'readme/filter'
|
3
4
|
|
4
5
|
module Readme
|
5
6
|
module Har
|
6
7
|
class RequestSerializer
|
7
|
-
def initialize(request, filter = Filter::None.new)
|
8
|
+
def initialize(request, filter = Readme::Filter::None.new)
|
8
9
|
@request = request
|
9
10
|
@filter = filter
|
10
11
|
end
|
@@ -13,11 +14,11 @@ module Readme
|
|
13
14
|
{
|
14
15
|
method: @request.request_method,
|
15
16
|
queryString: Har::Collection.new(@filter, @request.query_params).to_a,
|
16
|
-
url:
|
17
|
+
url: url,
|
17
18
|
httpVersion: @request.http_version,
|
18
19
|
headers: Har::Collection.new(@filter, @request.headers).to_a,
|
19
20
|
cookies: Har::Collection.new(@filter, @request.cookies).to_a,
|
20
|
-
postData:
|
21
|
+
postData: post_data,
|
21
22
|
headersSize: -1,
|
22
23
|
bodySize: @request.content_length
|
23
24
|
}.compact
|
@@ -25,7 +26,17 @@ module Readme
|
|
25
26
|
|
26
27
|
private
|
27
28
|
|
28
|
-
def
|
29
|
+
def url
|
30
|
+
url = URI(@request.url)
|
31
|
+
headers = @request.headers
|
32
|
+
forward_proto = headers['X-Forwarded-Proto']
|
33
|
+
forward_host = headers['X-Forwarded-Host']
|
34
|
+
url.host = forward_host if forward_host.is_a?(String)
|
35
|
+
url.scheme = forward_proto if forward_proto.is_a?(String)
|
36
|
+
url.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
def post_data
|
29
40
|
if @request.content_type.nil?
|
30
41
|
nil
|
31
42
|
elsif @request.form_data?
|
@@ -48,18 +59,34 @@ module Readme
|
|
48
59
|
def request_body
|
49
60
|
if @filter.pass_through?
|
50
61
|
pass_through_body
|
51
|
-
|
52
|
-
|
53
|
-
|
62
|
+
elsif form_urlencoded?
|
63
|
+
form_urlencoded_body
|
64
|
+
elsif json?
|
54
65
|
json_body
|
66
|
+
else
|
67
|
+
@request.body
|
55
68
|
end
|
56
69
|
end
|
57
70
|
|
71
|
+
def json?
|
72
|
+
['application/json', 'application/x-json', 'text/json', 'text/x-json']
|
73
|
+
.include?(@request.content_type) || @request.content_type.include?('+json')
|
74
|
+
end
|
75
|
+
|
76
|
+
def form_urlencoded?
|
77
|
+
@request.content_type == 'application/x-www-form-urlencoded'
|
78
|
+
end
|
79
|
+
|
58
80
|
def json_body
|
59
81
|
parsed_body = JSON.parse(@request.body)
|
60
82
|
Har::Collection.new(@filter, parsed_body).to_h.to_json
|
61
83
|
end
|
62
84
|
|
85
|
+
def form_urlencoded_body
|
86
|
+
parsed_body = CGI.parse(@request.body).transform_values(&:first)
|
87
|
+
Har::Collection.new(@filter, parsed_body).to_h.to_json
|
88
|
+
end
|
89
|
+
|
63
90
|
def pass_through_body
|
64
91
|
@request.body
|
65
92
|
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'rack/utils'
|
2
|
+
require 'readme/har/collection'
|
3
3
|
|
4
4
|
module Readme
|
5
5
|
module Har
|
@@ -37,7 +37,7 @@ module Readme
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def empty_content
|
40
|
-
{mimeType:
|
40
|
+
{ mimeType: '', size: 0 }
|
41
41
|
end
|
42
42
|
|
43
43
|
def json_content
|
@@ -1,13 +1,15 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
require 'readme/metrics'
|
5
|
+
require 'readme/har/request_serializer'
|
6
|
+
require 'readme/har/response_serializer'
|
7
|
+
require 'readme/har/collection'
|
6
8
|
|
7
9
|
module Readme
|
8
10
|
module Har
|
9
11
|
class Serializer
|
10
|
-
HAR_VERSION =
|
12
|
+
HAR_VERSION = '1.2'
|
11
13
|
|
12
14
|
def initialize(request, response, start_time, end_time, filter)
|
13
15
|
@http_request = request
|
@@ -17,7 +19,7 @@ module Readme
|
|
17
19
|
@filter = filter
|
18
20
|
end
|
19
21
|
|
20
|
-
def to_json
|
22
|
+
def to_json(*_args)
|
21
23
|
{
|
22
24
|
log: {
|
23
25
|
version: HAR_VERSION,
|
@@ -31,8 +33,9 @@ module Readme
|
|
31
33
|
|
32
34
|
def creator
|
33
35
|
{
|
34
|
-
name:
|
35
|
-
version: Readme::Metrics::VERSION
|
36
|
+
name: 'readme-metrics (ruby)',
|
37
|
+
version: Readme::Metrics::VERSION,
|
38
|
+
comment: "#{RUBY_PLATFORM}/#{RUBY_VERSION}" # arm64-darwin21/2.7.2
|
36
39
|
}
|
37
40
|
end
|
38
41
|
|
data/lib/readme/http_request.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require_relative
|
1
|
+
require 'rack'
|
2
|
+
require 'rack/request'
|
3
|
+
require_relative 'content_type_helper'
|
4
4
|
|
5
5
|
module Readme
|
6
6
|
class HttpRequest
|
@@ -11,7 +11,7 @@ module Readme
|
|
11
11
|
Rack::HTTP_VERSION,
|
12
12
|
Rack::HTTP_HOST,
|
13
13
|
Rack::HTTP_PORT
|
14
|
-
]
|
14
|
+
].freeze
|
15
15
|
|
16
16
|
def initialize(env)
|
17
17
|
@request = Rack::Request.new(env)
|
@@ -50,7 +50,7 @@ module Readme
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def options?
|
53
|
-
@request.request_method ==
|
53
|
+
@request.request_method == 'OPTIONS'
|
54
54
|
end
|
55
55
|
|
56
56
|
def headers
|
@@ -82,20 +82,22 @@ module Readme
|
|
82
82
|
# Other "headers" like version and host are prefixed with `HTTP_` by Rack but
|
83
83
|
# don't seem to be considered legit HTTP headers.
|
84
84
|
def http_header?(name)
|
85
|
-
name.start_with?(
|
85
|
+
name.start_with?('HTTP') && !HTTP_NON_HEADERS.include?(name)
|
86
86
|
end
|
87
87
|
|
88
88
|
# Headers like `Content-Type: application/json` come into rack like
|
89
89
|
# `"HTTP_CONTENT_TYPE" => "application/json"`.
|
90
90
|
def normalize_header_name(header)
|
91
|
-
header.delete_prefix(
|
91
|
+
header.delete_prefix('HTTP_').split('_').map(&:capitalize).join('-')
|
92
92
|
end
|
93
93
|
|
94
94
|
# These special headers are explicitly _not_ prefixed with HTTP_ in the Rack
|
95
95
|
# env so we need to add them in manually
|
96
96
|
def unprefixed_headers
|
97
|
-
{
|
98
|
-
|
97
|
+
{
|
98
|
+
'Content-Type' => @request.content_type,
|
99
|
+
'Content-Length' => @request.content_length
|
100
|
+
}.compact
|
99
101
|
end
|
100
102
|
end
|
101
103
|
end
|
data/lib/readme/http_response.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require_relative
|
1
|
+
require 'rack'
|
2
|
+
require 'rack/response'
|
3
|
+
require_relative 'content_type_helper'
|
4
4
|
|
5
5
|
module Readme
|
6
6
|
class HttpResponse < SimpleDelegator
|
@@ -13,22 +13,22 @@ module Readme
|
|
13
13
|
def body
|
14
14
|
if raw_body.respond_to?(:rewind)
|
15
15
|
raw_body.rewind
|
16
|
-
content = raw_body.each.reduce(
|
16
|
+
content = raw_body.each.reduce('', :+)
|
17
17
|
raw_body.rewind
|
18
18
|
|
19
19
|
content
|
20
20
|
else
|
21
|
-
raw_body.each.reduce(
|
21
|
+
raw_body.each.reduce('', :+)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
25
|
def content_length
|
26
26
|
if empty_body_status?
|
27
27
|
0
|
28
|
-
elsif !headers[
|
28
|
+
elsif !headers['Content-Length']
|
29
29
|
body.bytesize
|
30
30
|
else
|
31
|
-
headers[
|
31
|
+
headers['Content-Length'].to_i
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
data/lib/readme/metrics.rb
CHANGED
@@ -1,21 +1,21 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'readme/metrics/version'
|
4
|
+
require 'readme/har/serializer'
|
5
|
+
require 'readme/filter'
|
6
|
+
require 'readme/payload'
|
7
|
+
require 'readme/request_queue'
|
8
|
+
require 'readme/errors'
|
9
|
+
require 'readme/http_request'
|
10
|
+
require 'readme/http_response'
|
11
|
+
require 'httparty'
|
12
|
+
require 'logger'
|
11
13
|
|
12
14
|
module Readme
|
13
15
|
class Metrics
|
14
|
-
SDK_NAME =
|
15
|
-
DEFAULT_BUFFER_LENGTH =
|
16
|
-
ENDPOINT =
|
17
|
-
USER_INFO_KEYS = [:api_key, :label, :email]
|
18
|
-
USER_INFO_KEYS_DEPRECATED = [:id, :label, :email]
|
16
|
+
SDK_NAME = 'readme-metrics'
|
17
|
+
DEFAULT_BUFFER_LENGTH = 1
|
18
|
+
ENDPOINT = 'https://metrics.readme.io/v1/request'
|
19
19
|
|
20
20
|
def self.logger
|
21
21
|
@@logger
|
@@ -65,16 +65,17 @@ module Readme
|
|
65
65
|
request = HttpRequest.new(env)
|
66
66
|
har = Har::Serializer.new(request, response, start_time, end_time, @filter)
|
67
67
|
user_info = @get_user_info.call(env)
|
68
|
+
ip = env['REMOTE_ADDR']
|
68
69
|
|
69
70
|
if !user_info_valid?(user_info)
|
70
71
|
Readme::Metrics.logger.warn Errors.bad_block_message(user_info)
|
71
72
|
elsif request.options?
|
72
|
-
Readme::Metrics.logger.info
|
73
|
+
Readme::Metrics.logger.info 'OPTIONS request omitted from ReadMe API logging'
|
73
74
|
elsif !can_filter? request, response
|
74
75
|
Readme::Metrics.logger.warn "Request or response body MIME type isn't supported for filtering. Omitting request from ReadMe API logging"
|
75
76
|
else
|
76
|
-
payload = Payload.new(har, user_info, development: @development)
|
77
|
-
@@request_queue.push(payload.to_json)
|
77
|
+
payload = Payload.new(har, user_info, ip, development: @development)
|
78
|
+
@@request_queue.push(payload.to_json) unless payload.ignore
|
78
79
|
end
|
79
80
|
end
|
80
81
|
|
@@ -109,36 +110,36 @@ module Readme
|
|
109
110
|
raise Errors::ConfigurationError, Errors::BUFFER_LENGTH_ERROR
|
110
111
|
end
|
111
112
|
|
112
|
-
if options[:development] && !
|
113
|
+
if options[:development] && !a_boolean?(options[:development])
|
113
114
|
raise Errors::ConfigurationError, Errors::DEVELOPMENT_ERROR
|
114
115
|
end
|
115
116
|
|
116
|
-
if options[:logger] &&
|
117
|
+
if options[:logger] && logger_inferface?(options[:logger])
|
117
118
|
raise Errors::ConfigurationError, Errors::LOGGER_ERROR
|
118
119
|
end
|
119
120
|
end
|
120
121
|
|
121
|
-
def
|
122
|
-
[
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
122
|
+
def logger_inferface?(logger)
|
123
|
+
%i[
|
124
|
+
unknown
|
125
|
+
fatal
|
126
|
+
error
|
127
|
+
warn
|
128
|
+
info
|
129
|
+
debug
|
129
130
|
].any? { |message| !logger.respond_to? message }
|
130
131
|
end
|
131
132
|
|
132
|
-
def
|
133
|
-
|
133
|
+
def a_boolean?(arg)
|
134
|
+
[true, false].include?(arg)
|
134
135
|
end
|
135
136
|
|
137
|
+
# rubocop:disable Style/InverseMethods
|
136
138
|
def user_info_valid?(user_info)
|
137
|
-
sorted_user_info_keys = user_info.keys.sort
|
138
139
|
!user_info.nil? &&
|
139
140
|
!user_info.values.any?(&:nil?) &&
|
140
|
-
(
|
141
|
-
sorted_user_info_keys === USER_INFO_KEYS_DEPRECATED.sort)
|
141
|
+
user_info.key?(:api_key) || user_info.key?(:id)
|
142
142
|
end
|
143
|
+
# rubocop:enable Style/InverseMethods
|
143
144
|
end
|
144
145
|
end
|
data/lib/readme/payload.rb
CHANGED
@@ -1,17 +1,26 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'uuid'
|
3
|
+
|
1
4
|
module Readme
|
2
5
|
class Payload
|
3
|
-
|
6
|
+
attr_reader :ignore
|
7
|
+
|
8
|
+
def initialize(har, info, ip_address, development:)
|
4
9
|
@har = har
|
5
|
-
|
6
|
-
user_info[:id] =
|
7
|
-
@
|
10
|
+
@user_info = info.slice(:id, :label, :email)
|
11
|
+
@user_info[:id] = info[:api_key] unless info[:api_key].nil? # swap api_key for id if api_key is present
|
12
|
+
@log_id = info[:log_id]
|
13
|
+
@ignore = info[:ignore]
|
14
|
+
@ip_address = ip_address
|
8
15
|
@development = development
|
16
|
+
@uuid = UUID.new
|
9
17
|
end
|
10
18
|
|
11
|
-
def to_json
|
19
|
+
def to_json(*_args)
|
12
20
|
{
|
21
|
+
logId: UUID.validate(@log_id) ? @log_id : @uuid.generate,
|
13
22
|
group: @user_info,
|
14
|
-
clientIPAddress:
|
23
|
+
clientIPAddress: @ip_address,
|
15
24
|
development: @development,
|
16
25
|
request: JSON.parse(@har.to_json)
|
17
26
|
}.to_json
|
data/lib/readme/request_queue.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'readme/metrics'
|
2
2
|
|
3
3
|
module Readme
|
4
4
|
class RequestQueue
|
@@ -30,8 +30,8 @@ module Readme
|
|
30
30
|
Thread.new do
|
31
31
|
HTTParty.post(
|
32
32
|
Readme::Metrics::ENDPOINT,
|
33
|
-
basic_auth: {username: @api_key, password:
|
34
|
-
headers: {
|
33
|
+
basic_auth: { username: @api_key, password: '' },
|
34
|
+
headers: { 'Content-Type' => 'application/json' },
|
35
35
|
body: to_json(payloads)
|
36
36
|
)
|
37
37
|
end
|
@@ -42,7 +42,7 @@ module Readme
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def to_json(payloads)
|
45
|
-
"[#{payloads.join(
|
45
|
+
"[#{payloads.join(', ')}]"
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|
data/readme-metrics.gemspec
CHANGED
@@ -1,29 +1,31 @@
|
|
1
|
-
require_relative
|
1
|
+
require_relative 'lib/readme/metrics/version'
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
|
-
spec.name =
|
4
|
+
spec.name = 'readme-metrics'
|
5
5
|
spec.version = Readme::Metrics::VERSION
|
6
|
-
spec.authors = [
|
7
|
-
spec.email = [
|
8
|
-
spec.license =
|
6
|
+
spec.authors = ['ReadMe']
|
7
|
+
spec.email = ['support@readme.io']
|
8
|
+
spec.license = 'ISC'
|
9
9
|
|
10
10
|
spec.summary = "SDK for Readme's metrics API"
|
11
11
|
spec.description = "Middleware for logging requests to Readme's metrics API"
|
12
|
-
spec.homepage =
|
13
|
-
spec.required_ruby_version = Gem::Requirement.new(
|
12
|
+
spec.homepage = 'https://docs.readme.com/metrics/docs/getting-started-with-api-metrics'
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
|
14
14
|
|
15
|
-
spec.metadata[
|
16
|
-
spec.metadata[
|
17
|
-
spec.metadata[
|
15
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
16
|
+
spec.metadata['source_code_uri'] = 'https://github.com/readmeio/metrics-sdks/tree/main/packages/ruby'
|
17
|
+
spec.metadata['changelog_uri'] = 'https://github.com/readmeio/metrics-sdks/blob/main/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.
|
21
|
-
spec.files = Dir.chdir(File.expand_path(
|
21
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
22
22
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
23
23
|
end
|
24
|
+
|
24
25
|
# spec.bindir = "exe"
|
25
26
|
# spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
|
-
spec.require_paths = [
|
27
|
+
spec.require_paths = ['lib']
|
27
28
|
|
28
|
-
spec.add_runtime_dependency
|
29
|
+
spec.add_runtime_dependency 'httparty', '~> 0.18'
|
30
|
+
spec.add_runtime_dependency 'uuid', '~> 2.3.8'
|
29
31
|
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:
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ReadMe
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-08-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0.18'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: uuid
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.3.8
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.3.8
|
27
41
|
description: Middleware for logging requests to Readme's metrics API
|
28
42
|
email:
|
29
43
|
- support@readme.io
|
@@ -33,9 +47,11 @@ extra_rdoc_files: []
|
|
33
47
|
files:
|
34
48
|
- ".gitignore"
|
35
49
|
- ".rspec"
|
50
|
+
- ".rubocop.yml"
|
36
51
|
- Gemfile
|
37
52
|
- Gemfile.lock
|
38
53
|
- LICENSE
|
54
|
+
- Makefile
|
39
55
|
- README.md
|
40
56
|
- Rakefile
|
41
57
|
- bin/console
|
@@ -59,9 +75,9 @@ licenses:
|
|
59
75
|
- ISC
|
60
76
|
metadata:
|
61
77
|
homepage_uri: https://docs.readme.com/metrics/docs/getting-started-with-api-metrics
|
62
|
-
source_code_uri: https://github.com/readmeio/metrics-sdks/
|
63
|
-
changelog_uri: https://github.com/readmeio/metrics-sdks/blob/main/
|
64
|
-
post_install_message:
|
78
|
+
source_code_uri: https://github.com/readmeio/metrics-sdks/tree/main/packages/ruby
|
79
|
+
changelog_uri: https://github.com/readmeio/metrics-sdks/blob/main/CHANGELOG.md
|
80
|
+
post_install_message:
|
65
81
|
rdoc_options: []
|
66
82
|
require_paths:
|
67
83
|
- lib
|
@@ -76,8 +92,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
92
|
- !ruby/object:Gem::Version
|
77
93
|
version: '0'
|
78
94
|
requirements: []
|
79
|
-
rubygems_version: 3.1
|
80
|
-
signing_key:
|
95
|
+
rubygems_version: 3.0.3.1
|
96
|
+
signing_key:
|
81
97
|
specification_version: 4
|
82
98
|
summary: SDK for Readme's metrics API
|
83
99
|
test_files: []
|