readme-metrics 2.0.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 -9
- data/Gemfile.lock +8 -10
- data/LICENSE +1 -1
- data/Makefile +14 -0
- data/README.md +21 -102
- 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 +14 -14
- data/lib/readme/har/response_serializer.rb +3 -3
- data/lib/readme/har/serializer.rb +11 -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 +32 -41
- data/lib/readme/payload.rb +6 -4
- data/lib/readme/request_queue.rb +4 -4
- data/readme-metrics.gemspec +15 -15
- metadata +10 -23
- data/SECURITY.md +0 -12
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,15 +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
|
14
|
-
gem
|
15
|
-
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
@@ -3,7 +3,6 @@ PATH
|
|
3
3
|
specs:
|
4
4
|
readme-metrics (2.0.1)
|
5
5
|
httparty (~> 0.18)
|
6
|
-
os (~> 1.1.4)
|
7
6
|
uuid (~> 2.3.8)
|
8
7
|
|
9
8
|
GEM
|
@@ -27,12 +26,11 @@ GEM
|
|
27
26
|
mime-types-data (~> 3.2015)
|
28
27
|
mime-types-data (3.2022.0105)
|
29
28
|
multi_xml (0.6.0)
|
30
|
-
os (1.1.4)
|
31
29
|
parallel (1.19.2)
|
32
30
|
parser (2.7.1.4)
|
33
31
|
ast (~> 2.4.1)
|
34
32
|
public_suffix (4.0.6)
|
35
|
-
rack (2.2.3)
|
33
|
+
rack (2.2.3.1)
|
36
34
|
rack-test (1.1.0)
|
37
35
|
rack (>= 1.0, < 3)
|
38
36
|
rainbow (3.0.0)
|
@@ -63,13 +61,12 @@ GEM
|
|
63
61
|
unicode-display_width (>= 1.4.0, < 2.0)
|
64
62
|
rubocop-ast (0.3.0)
|
65
63
|
parser (>= 2.7.1.4)
|
66
|
-
rubocop-performance (1.
|
67
|
-
rubocop (>= 0.
|
64
|
+
rubocop-performance (1.7.1)
|
65
|
+
rubocop (>= 0.82.0)
|
66
|
+
rubocop-rspec (1.41.0)
|
67
|
+
rubocop (>= 0.68.1)
|
68
68
|
ruby-progressbar (1.10.1)
|
69
69
|
safe_yaml (1.0.5)
|
70
|
-
standard (0.4.7)
|
71
|
-
rubocop (~> 0.85.0)
|
72
|
-
rubocop-performance (~> 1.6.0)
|
73
70
|
systemu (2.6.5)
|
74
71
|
unicode-display_width (1.7.0)
|
75
72
|
uuid (2.3.9)
|
@@ -84,12 +81,13 @@ PLATFORMS
|
|
84
81
|
|
85
82
|
DEPENDENCIES
|
86
83
|
json-schema
|
87
|
-
os
|
88
84
|
rack-test
|
89
85
|
rake (~> 12.0)
|
90
86
|
readme-metrics!
|
91
87
|
rspec (~> 3.0)
|
92
|
-
|
88
|
+
rubocop
|
89
|
+
rubocop-performance
|
90
|
+
rubocop-rspec
|
93
91
|
uuid
|
94
92
|
webmock
|
95
93
|
|
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,111 +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
|
-
### SDK Options
|
29
|
-
|
30
|
-
Option | Type | Description
|
31
|
-
-----------------|------------------|---------
|
32
|
-
`reject_params` | Array of strings | If you have sensitive data you'd like to prevent from being sent to the Metrics API via headers, query params or payload bodies, you can specify a list of keys to filter via the `reject_params` option. NOTE: cannot be used in conjunction with `allow_only`. You may only specify either `reject_params` or `allow_only` keys, not both.
|
33
|
-
`allow_only` | Array of strings | The inverse of `reject_params`. If included all parameters but those in this list will be redacted. NOTE: cannot be used in conjunction with `reject_params`. You may only specify either `reject_params` or `allow_only` keys, not both.
|
34
|
-
`development` | bool | Defaults to `false`. When `true`, the log will be marked as a development log. This is great for separating staging or test data from data coming from customers.
|
35
|
-
`buffer_length` | number | Defaults to `1`. This value should be a number representing the amount of requests to group up before sending them over the network. Increasing this value may increase performance by batching, but will also delay the time until logs show up in the dashboard given the buffer size needs to be reached in order for the logs to be sent.
|
36
|
-
|
37
|
-
### Payload Data
|
38
|
-
|
39
|
-
Option | Required? | Type | Description
|
40
|
-
--------------------|-----------|------------------|----------
|
41
|
-
`api_key` | yes | string | API Key used to make the request. Note that this is different from the `readmeAPIKey` described above in the options data. This should be a value from your API that is unique to each of your users.
|
42
|
-
`label` | no | string | This will be the user's display name in the API Metrics Dashboard, since it's much easier to remember a name than an API key.
|
43
|
-
`email` | no | string | Email of the user that is making the call.
|
44
|
-
`log_id` | no | string | A UUIDv4 identifier. If not provided this will be automatically generated for you. Providing your own `log_id` is useful if you want to know the URL of the log in advance, i.e. `{your_base_url}/logs/{your_log_id}`.
|
45
|
-
`ignore` | no | bool | A flag that when set to `true` will suppress sending the log.
|
46
|
-
|
47
|
-
### Rails
|
48
|
-
|
49
|
-
```ruby
|
50
|
-
# config/environments/development.rb or config/environments/production.rb
|
51
|
-
require "readme/metrics"
|
52
|
-
|
53
|
-
sdk_options = {
|
54
|
-
api_key: "<<apiKey>>",
|
55
|
-
development: false,
|
56
|
-
reject_params: ["not_included", "dont_send"],
|
57
|
-
buffer_length: 5,
|
58
|
-
}
|
59
|
-
|
60
|
-
config.middleware.use Readme::Metrics, sdk_options do |env|
|
61
|
-
current_user = env['warden'].authenticate
|
62
|
-
|
63
|
-
payload_data = current_user.present? ? {
|
64
|
-
api_key: current_user.api_key, # Not the same as the ReadMe API Key
|
65
|
-
label: current_user.name,
|
66
|
-
email: current_user.email
|
67
|
-
} : {
|
68
|
-
api_key: "guest",
|
69
|
-
label: "Guest User",
|
70
|
-
email: "guest@example.com"
|
71
|
-
}
|
72
|
-
|
73
|
-
payload_data
|
74
|
-
end
|
22
|
+
```bash
|
23
|
+
gem "readme-metrics"
|
75
24
|
```
|
76
25
|
|
77
|
-
|
78
|
-
|
79
|
-
```ruby
|
80
|
-
# config.ru
|
81
|
-
sdk_options = {
|
82
|
-
api_key: "<<apiKey>>",
|
83
|
-
development: false,
|
84
|
-
reject_params: ["not_included", "dont_send"]
|
85
|
-
}
|
86
|
-
|
87
|
-
use Readme::Metrics, sdk_options do |env|
|
88
|
-
{
|
89
|
-
api_key: "owlbert_api_key"
|
90
|
-
label: "Owlbert",
|
91
|
-
email: "owlbert@example.com",
|
92
|
-
log_id: SecureRandom.uuid
|
93
|
-
}
|
94
|
-
end
|
95
|
-
|
96
|
-
run YourApp.new
|
97
|
-
```
|
98
|
-
|
99
|
-
### Sample Applications
|
100
|
-
|
101
|
-
- [Rails](https://github.com/readmeio/metrics-sdk-rails-sample)
|
102
|
-
- [Rack](https://github.com/readmeio/metrics-sdk-racks-sample)
|
103
|
-
- [Sinatra](https://github.com/readmeio/metrics-sdk-sinatra-example)
|
104
|
-
|
105
|
-
### Contributing
|
106
|
-
|
107
|
-
Ensure you are running the version of ruby specified in the `Gemfile.lock`; use `rvm` to easy manage ruby versions. Run `bundle` to install dependencies, `rake` or `rspec` to ensure tests pass, and `bundle exec standardrb` to lint the code.
|
108
|
-
|
109
|
-
## License
|
26
|
+
**For more information on setup, check out our [integration documentation](https://docs.readme.com/docs/ruby-api-metrics-set-up).**
|
110
27
|
|
111
|
-
|
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,6 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require 'cgi'
|
2
|
+
require 'readme/har/collection'
|
3
|
+
require 'readme/filter'
|
4
4
|
|
5
5
|
module Readme
|
6
6
|
module Har
|
@@ -18,7 +18,7 @@ module Readme
|
|
18
18
|
httpVersion: @request.http_version,
|
19
19
|
headers: Har::Collection.new(@filter, @request.headers).to_a,
|
20
20
|
cookies: Har::Collection.new(@filter, @request.cookies).to_a,
|
21
|
-
postData:
|
21
|
+
postData: post_data,
|
22
22
|
headersSize: -1,
|
23
23
|
bodySize: @request.content_length
|
24
24
|
}.compact
|
@@ -29,14 +29,14 @@ module Readme
|
|
29
29
|
def url
|
30
30
|
url = URI(@request.url)
|
31
31
|
headers = @request.headers
|
32
|
-
forward_proto = headers[
|
33
|
-
forward_host = headers[
|
32
|
+
forward_proto = headers['X-Forwarded-Proto']
|
33
|
+
forward_host = headers['X-Forwarded-Host']
|
34
34
|
url.host = forward_host if forward_host.is_a?(String)
|
35
35
|
url.scheme = forward_proto if forward_proto.is_a?(String)
|
36
36
|
url.to_s
|
37
37
|
end
|
38
38
|
|
39
|
-
def
|
39
|
+
def post_data
|
40
40
|
if @request.content_type.nil?
|
41
41
|
nil
|
42
42
|
elsif @request.form_data?
|
@@ -59,22 +59,22 @@ module Readme
|
|
59
59
|
def request_body
|
60
60
|
if @filter.pass_through?
|
61
61
|
pass_through_body
|
62
|
-
elsif
|
62
|
+
elsif form_urlencoded?
|
63
63
|
form_urlencoded_body
|
64
|
-
elsif
|
64
|
+
elsif json?
|
65
65
|
json_body
|
66
66
|
else
|
67
67
|
@request.body
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
-
def
|
72
|
-
[
|
73
|
-
.include?(@request.content_type) || @request.content_type.include?(
|
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
74
|
end
|
75
75
|
|
76
|
-
def
|
77
|
-
@request.content_type ==
|
76
|
+
def form_urlencoded?
|
77
|
+
@request.content_type == 'application/x-www-form-urlencoded'
|
78
78
|
end
|
79
79
|
|
80
80
|
def json_body
|
@@ -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,9 +33,9 @@ module Readme
|
|
31
33
|
|
32
34
|
def creator
|
33
35
|
{
|
34
|
-
name:
|
36
|
+
name: 'readme-metrics (ruby)',
|
35
37
|
version: Readme::Metrics::VERSION,
|
36
|
-
comment: "#{
|
38
|
+
comment: "#{RUBY_PLATFORM}/#{RUBY_VERSION}" # arm64-darwin21/2.7.2
|
37
39
|
}
|
38
40
|
end
|
39
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,33 +1,21 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
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'
|
12
13
|
|
13
14
|
module Readme
|
14
15
|
class Metrics
|
15
|
-
|
16
|
-
if OS.windows?
|
17
|
-
"windows"
|
18
|
-
elsif OS.mac?
|
19
|
-
"mac"
|
20
|
-
elsif OS.linux?
|
21
|
-
"linux"
|
22
|
-
else
|
23
|
-
"unknown"
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
SDK_NAME = "Readme.io Ruby SDK"
|
28
|
-
PLATFORM = platform
|
16
|
+
SDK_NAME = 'readme-metrics'
|
29
17
|
DEFAULT_BUFFER_LENGTH = 1
|
30
|
-
ENDPOINT =
|
18
|
+
ENDPOINT = 'https://metrics.readme.io/v1/request'
|
31
19
|
|
32
20
|
def self.logger
|
33
21
|
@@logger
|
@@ -77,15 +65,16 @@ module Readme
|
|
77
65
|
request = HttpRequest.new(env)
|
78
66
|
har = Har::Serializer.new(request, response, start_time, end_time, @filter)
|
79
67
|
user_info = @get_user_info.call(env)
|
68
|
+
ip = env['REMOTE_ADDR']
|
80
69
|
|
81
70
|
if !user_info_valid?(user_info)
|
82
71
|
Readme::Metrics.logger.warn Errors.bad_block_message(user_info)
|
83
72
|
elsif request.options?
|
84
|
-
Readme::Metrics.logger.info
|
73
|
+
Readme::Metrics.logger.info 'OPTIONS request omitted from ReadMe API logging'
|
85
74
|
elsif !can_filter? request, response
|
86
75
|
Readme::Metrics.logger.warn "Request or response body MIME type isn't supported for filtering. Omitting request from ReadMe API logging"
|
87
76
|
else
|
88
|
-
payload = Payload.new(har, user_info, development: @development)
|
77
|
+
payload = Payload.new(har, user_info, ip, development: @development)
|
89
78
|
@@request_queue.push(payload.to_json) unless payload.ignore
|
90
79
|
end
|
91
80
|
end
|
@@ -121,34 +110,36 @@ module Readme
|
|
121
110
|
raise Errors::ConfigurationError, Errors::BUFFER_LENGTH_ERROR
|
122
111
|
end
|
123
112
|
|
124
|
-
if options[:development] && !
|
113
|
+
if options[:development] && !a_boolean?(options[:development])
|
125
114
|
raise Errors::ConfigurationError, Errors::DEVELOPMENT_ERROR
|
126
115
|
end
|
127
116
|
|
128
|
-
if options[:logger] &&
|
117
|
+
if options[:logger] && logger_inferface?(options[:logger])
|
129
118
|
raise Errors::ConfigurationError, Errors::LOGGER_ERROR
|
130
119
|
end
|
131
120
|
end
|
132
121
|
|
133
|
-
def
|
134
|
-
[
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
122
|
+
def logger_inferface?(logger)
|
123
|
+
%i[
|
124
|
+
unknown
|
125
|
+
fatal
|
126
|
+
error
|
127
|
+
warn
|
128
|
+
info
|
129
|
+
debug
|
141
130
|
].any? { |message| !logger.respond_to? message }
|
142
131
|
end
|
143
132
|
|
144
|
-
def
|
145
|
-
|
133
|
+
def a_boolean?(arg)
|
134
|
+
[true, false].include?(arg)
|
146
135
|
end
|
147
136
|
|
137
|
+
# rubocop:disable Style/InverseMethods
|
148
138
|
def user_info_valid?(user_info)
|
149
139
|
!user_info.nil? &&
|
150
140
|
!user_info.values.any?(&:nil?) &&
|
151
|
-
user_info.
|
141
|
+
user_info.key?(:api_key) || user_info.key?(:id)
|
152
142
|
end
|
143
|
+
# rubocop:enable Style/InverseMethods
|
153
144
|
end
|
154
145
|
end
|
data/lib/readme/payload.rb
CHANGED
@@ -1,24 +1,26 @@
|
|
1
|
-
require
|
1
|
+
require 'socket'
|
2
|
+
require 'uuid'
|
2
3
|
|
3
4
|
module Readme
|
4
5
|
class Payload
|
5
6
|
attr_reader :ignore
|
6
7
|
|
7
|
-
def initialize(har, info, development:)
|
8
|
+
def initialize(har, info, ip_address, development:)
|
8
9
|
@har = har
|
9
10
|
@user_info = info.slice(:id, :label, :email)
|
10
11
|
@user_info[:id] = info[:api_key] unless info[:api_key].nil? # swap api_key for id if api_key is present
|
11
12
|
@log_id = info[:log_id]
|
12
13
|
@ignore = info[:ignore]
|
14
|
+
@ip_address = ip_address
|
13
15
|
@development = development
|
14
16
|
@uuid = UUID.new
|
15
17
|
end
|
16
18
|
|
17
|
-
def to_json
|
19
|
+
def to_json(*_args)
|
18
20
|
{
|
19
21
|
logId: UUID.validate(@log_id) ? @log_id : @uuid.generate,
|
20
22
|
group: @user_info,
|
21
|
-
clientIPAddress:
|
23
|
+
clientIPAddress: @ip_address,
|
22
24
|
development: @development,
|
23
25
|
request: JSON.parse(@har.to_json)
|
24
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,31 +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
|
30
|
-
spec.add_runtime_dependency "os", "~> 1.1.4"
|
29
|
+
spec.add_runtime_dependency 'httparty', '~> 0.18'
|
30
|
+
spec.add_runtime_dependency 'uuid', '~> 2.3.8'
|
31
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: 2.0
|
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: 2022-
|
11
|
+
date: 2022-08-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -38,20 +38,6 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 2.3.8
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: os
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: 1.1.4
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 1.1.4
|
55
41
|
description: Middleware for logging requests to Readme's metrics API
|
56
42
|
email:
|
57
43
|
- support@readme.io
|
@@ -61,12 +47,13 @@ extra_rdoc_files: []
|
|
61
47
|
files:
|
62
48
|
- ".gitignore"
|
63
49
|
- ".rspec"
|
50
|
+
- ".rubocop.yml"
|
64
51
|
- Gemfile
|
65
52
|
- Gemfile.lock
|
66
53
|
- LICENSE
|
54
|
+
- Makefile
|
67
55
|
- README.md
|
68
56
|
- Rakefile
|
69
|
-
- SECURITY.md
|
70
57
|
- bin/console
|
71
58
|
- bin/setup
|
72
59
|
- lib/readme/content_type_helper.rb
|
@@ -88,9 +75,9 @@ licenses:
|
|
88
75
|
- ISC
|
89
76
|
metadata:
|
90
77
|
homepage_uri: https://docs.readme.com/metrics/docs/getting-started-with-api-metrics
|
91
|
-
source_code_uri: https://github.com/readmeio/metrics-sdks/
|
92
|
-
changelog_uri: https://github.com/readmeio/metrics-sdks/blob/main/
|
93
|
-
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:
|
94
81
|
rdoc_options: []
|
95
82
|
require_paths:
|
96
83
|
- lib
|
@@ -105,8 +92,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
105
92
|
- !ruby/object:Gem::Version
|
106
93
|
version: '0'
|
107
94
|
requirements: []
|
108
|
-
rubygems_version: 3.1
|
109
|
-
signing_key:
|
95
|
+
rubygems_version: 3.0.3.1
|
96
|
+
signing_key:
|
110
97
|
specification_version: 4
|
111
98
|
summary: SDK for Readme's metrics API
|
112
99
|
test_files: []
|
data/SECURITY.md
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
# Security Policy
|
2
|
-
|
3
|
-
## Reporting a Vulnerability
|
4
|
-
|
5
|
-
If there are any vulnerabilities in `readme-metrics`, don't hesitate to _report them_.
|
6
|
-
|
7
|
-
Please email security@readme.io and describe what you've found.
|
8
|
-
|
9
|
-
- If you have a fix, explain or attach it.
|
10
|
-
- In the near time, expect a reply with the required steps. Also, there may be a demand for a pull request which include the fixes.
|
11
|
-
|
12
|
-
> You should not disclose the vulnerability publicly if you haven't received an answer in some weeks. If the vulnerability is rejected, you may post it publicly within some hour of rejection, unless the rejection is withdrawn within that time period. After the vulnerability has been fixed, you may disclose the vulnerability details publicly over some days.
|