readme-metrics 0.2.0 → 1.0.0
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 +1 -1
- data/README.md +32 -20
- data/lib/content_type_helper.rb +15 -0
- data/lib/http_request.rb +11 -0
- data/lib/http_response.rb +43 -0
- data/lib/readme/filter.rb +16 -4
- data/lib/readme/har/request_serializer.rb +13 -1
- data/lib/readme/har/response_serializer.rb +13 -29
- data/lib/readme/metrics.rb +23 -3
- data/lib/readme/metrics/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 17dad1b40b974d05ae3d57a664e6f95c393cbaf8e807071631c11800b2ee118c
|
4
|
+
data.tar.gz: 2d3595d321dea5bac6b5577180f608a2f9045e333592e5c33cca4c52a3e997ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a586f8ae1273269473451ea60bfeccb5e9fd01b6fa4ddacfd99b5918dfed24aa340a9e61aba6036a96b572c1eef50c4d2e1a9cd87162442197a02a4ff2456b2c
|
7
|
+
data.tar.gz: a7d894c039bdc676164f1dcd083650ae8ce026152baad7d83ea790a3ee4850e4de59246e922a641d6446943389c456557c1712b8b34c2085d4398e226c323c86
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -47,7 +47,7 @@ You may only specify either `reject_params` or `allow_only` keys, not both.
|
|
47
47
|
### Rails
|
48
48
|
|
49
49
|
```ruby
|
50
|
-
#
|
50
|
+
# config/environments/development.rb or config/environments/production.rb
|
51
51
|
require "readme/metrics"
|
52
52
|
|
53
53
|
options = {
|
@@ -58,39 +58,51 @@ options = {
|
|
58
58
|
}
|
59
59
|
|
60
60
|
config.middleware.use Readme::Metrics, options do |env|
|
61
|
-
current_user = env['warden'].authenticate
|
61
|
+
current_user = env['warden'].authenticate
|
62
62
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
63
|
+
if current_user.present?
|
64
|
+
{
|
65
|
+
id: current_user.id,
|
66
|
+
label: current_user.name,
|
67
|
+
email: current_user.email
|
68
|
+
}
|
69
|
+
else
|
70
|
+
{
|
71
|
+
id: "guest",
|
72
|
+
label: "Guest User",
|
73
|
+
email: "guest@example.com"
|
74
|
+
}
|
75
|
+
end
|
68
76
|
end
|
69
77
|
```
|
70
78
|
|
71
|
-
### Rack
|
79
|
+
### Rack
|
72
80
|
|
73
81
|
```ruby
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
+
# config.ru
|
83
|
+
options = {
|
84
|
+
api_key: "YOUR_API_KEY",
|
85
|
+
development: false,
|
86
|
+
reject_params: ["not_included", "dont_send"]
|
87
|
+
}
|
88
|
+
|
89
|
+
use Readme::Metrics, options do |env|
|
82
90
|
{
|
83
91
|
id: "my_application_id"
|
84
92
|
label: "My Application",
|
85
93
|
email: "my.application@example.com"
|
86
94
|
}
|
87
|
-
end
|
88
|
-
builder.run your_app
|
89
95
|
end
|
96
|
+
|
97
|
+
run YourApp.new
|
90
98
|
```
|
91
99
|
|
92
|
-
|
100
|
+
### Sample Applications
|
93
101
|
|
94
|
-
[
|
102
|
+
- [Rails](https://github.com/readmeio/metrics-sdk-rails-sample)
|
103
|
+
- [Rack](https://github.com/readmeio/metrics-sdk-racks-sample)
|
104
|
+
- [Sinatra](https://github.com/readmeio/metrics-sdk-sinatra-example)
|
95
105
|
|
106
|
+
## License
|
96
107
|
|
108
|
+
[View our license here](https://github.com/readmeio/metrics-sdks/tree/master/packages/ruby/LICENSE)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ContentTypeHelper
|
2
|
+
# Assumes the includer has a `content_type` method defined.
|
3
|
+
|
4
|
+
JSON_MIME_TYPES = [
|
5
|
+
"application/json",
|
6
|
+
"application/x-json",
|
7
|
+
"text/json",
|
8
|
+
"text/x-json",
|
9
|
+
"+json"
|
10
|
+
]
|
11
|
+
|
12
|
+
def json?
|
13
|
+
JSON_MIME_TYPES.any? { |mime_type| content_type.include?(mime_type) }
|
14
|
+
end
|
15
|
+
end
|
data/lib/http_request.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
require "rack"
|
2
2
|
require "rack/request"
|
3
|
+
require "content_type_helper"
|
3
4
|
|
4
5
|
class HttpRequest
|
6
|
+
include ContentTypeHelper
|
7
|
+
|
5
8
|
HTTP_NON_HEADERS = [
|
6
9
|
Rack::HTTP_COOKIE,
|
7
10
|
Rack::HTTP_VERSION,
|
@@ -55,6 +58,7 @@ class HttpRequest
|
|
55
58
|
.select { |key, _| http_header?(key) }
|
56
59
|
.to_h
|
57
60
|
.transform_keys { |header| normalize_header_name(header) }
|
61
|
+
.merge unprefixed_headers
|
58
62
|
end
|
59
63
|
|
60
64
|
def body
|
@@ -85,4 +89,11 @@ class HttpRequest
|
|
85
89
|
def normalize_header_name(header)
|
86
90
|
header.delete_prefix("HTTP_").split("_").map(&:capitalize).join("-")
|
87
91
|
end
|
92
|
+
|
93
|
+
# These special headers are explicitly _not_ prefixed with HTTP_ in the Rack
|
94
|
+
# env so we need to add them in manually
|
95
|
+
def unprefixed_headers
|
96
|
+
{"Content-Type" => @request.content_type,
|
97
|
+
"Content-Length" => @request.content_length}.compact
|
98
|
+
end
|
88
99
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "rack"
|
2
|
+
require "rack/response"
|
3
|
+
require "content_type_helper"
|
4
|
+
|
5
|
+
class HttpResponse < SimpleDelegator
|
6
|
+
include ContentTypeHelper
|
7
|
+
|
8
|
+
def self.from_parts(status, headers, body)
|
9
|
+
new(Rack::Response.new(body, status, headers))
|
10
|
+
end
|
11
|
+
|
12
|
+
def body
|
13
|
+
if raw_body.respond_to?(:rewind)
|
14
|
+
raw_body.rewind
|
15
|
+
content = raw_body.each.reduce("", :+)
|
16
|
+
raw_body.rewind
|
17
|
+
|
18
|
+
content
|
19
|
+
else
|
20
|
+
raw_body.each.reduce("", :+)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def content_length
|
25
|
+
if empty_body_status?
|
26
|
+
0
|
27
|
+
elsif !headers["Content-Length"]
|
28
|
+
body.bytesize
|
29
|
+
else
|
30
|
+
headers["Content-Length"].to_i
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def raw_body
|
37
|
+
__getobj__.body
|
38
|
+
end
|
39
|
+
|
40
|
+
def empty_body_status?
|
41
|
+
Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status.to_i)
|
42
|
+
end
|
43
|
+
end
|
data/lib/readme/filter.rb
CHANGED
@@ -13,21 +13,29 @@ class Filter
|
|
13
13
|
|
14
14
|
class AllowOnly
|
15
15
|
def initialize(filter_values)
|
16
|
-
@filter_values = filter_values
|
16
|
+
@filter_values = filter_values.map(&:downcase)
|
17
17
|
end
|
18
18
|
|
19
19
|
def filter(hash)
|
20
|
-
hash.select { |key, _value| @filter_values.include?(key) }
|
20
|
+
hash.select { |key, _value| @filter_values.include?(key.downcase) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def pass_through?
|
24
|
+
false
|
21
25
|
end
|
22
26
|
end
|
23
27
|
|
24
28
|
class RejectParams
|
25
29
|
def initialize(filter_values)
|
26
|
-
@filter_values = filter_values
|
30
|
+
@filter_values = filter_values.map(&:downcase)
|
27
31
|
end
|
28
32
|
|
29
33
|
def filter(hash)
|
30
|
-
hash.reject { |key, _value| @filter_values.include?(key) }
|
34
|
+
hash.reject { |key, _value| @filter_values.include?(key.downcase) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def pass_through?
|
38
|
+
false
|
31
39
|
end
|
32
40
|
end
|
33
41
|
|
@@ -35,6 +43,10 @@ class Filter
|
|
35
43
|
def filter(hash)
|
36
44
|
hash
|
37
45
|
end
|
46
|
+
|
47
|
+
def pass_through?
|
48
|
+
true
|
49
|
+
end
|
38
50
|
end
|
39
51
|
|
40
52
|
class FilterArgsError < StandardError
|
@@ -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
|
data/lib/readme/metrics.rb
CHANGED
@@ -5,6 +5,7 @@ require "readme/payload"
|
|
5
5
|
require "readme/request_queue"
|
6
6
|
require "readme/errors"
|
7
7
|
require "http_request"
|
8
|
+
require "http_response"
|
8
9
|
require "httparty"
|
9
10
|
require "logger"
|
10
11
|
|
@@ -40,16 +41,17 @@ module Readme
|
|
40
41
|
start_time = Time.now
|
41
42
|
status, headers, body = @app.call(env)
|
42
43
|
end_time = Time.now
|
43
|
-
response = Rack::Response.new(body, status, headers)
|
44
44
|
|
45
45
|
begin
|
46
|
+
response = HttpResponse.from_parts(status, headers, body)
|
46
47
|
process_response(
|
47
48
|
response: response,
|
48
49
|
env: env,
|
49
50
|
start_time: start_time,
|
50
51
|
end_time: end_time
|
51
52
|
)
|
52
|
-
rescue
|
53
|
+
rescue => e
|
54
|
+
Readme::Metrics.logger.warn "The following error occured when trying to log to the ReadMe metrics API: #{e.message}. Request not logged."
|
53
55
|
[status, headers, body]
|
54
56
|
end
|
55
57
|
|
@@ -64,15 +66,33 @@ module Readme
|
|
64
66
|
user_info = @get_user_info.call(env)
|
65
67
|
|
66
68
|
if user_info_invalid?(user_info)
|
67
|
-
Readme::Metrics.logger.
|
69
|
+
Readme::Metrics.logger.warn Errors.bad_block_message(user_info)
|
68
70
|
elsif request.options?
|
69
71
|
Readme::Metrics.logger.info "OPTIONS request omitted from ReadMe API logging"
|
72
|
+
elsif !can_filter? request, response
|
73
|
+
Readme::Metrics.logger.warn "Request or response body MIME type isn't supported for filtering. Omitting request from ReadMe API logging"
|
70
74
|
else
|
71
75
|
payload = Payload.new(har, user_info, development: @development)
|
72
76
|
@@request_queue.push(payload.to_json)
|
73
77
|
end
|
74
78
|
end
|
75
79
|
|
80
|
+
def can_filter?(request, response)
|
81
|
+
@filter.pass_through? || can_parse_bodies?(request, response)
|
82
|
+
end
|
83
|
+
|
84
|
+
def can_parse_bodies?(request, response)
|
85
|
+
parseable_request?(request) && parseable_response?(response)
|
86
|
+
end
|
87
|
+
|
88
|
+
def parseable_response?(response)
|
89
|
+
response.body.empty? || response.json?
|
90
|
+
end
|
91
|
+
|
92
|
+
def parseable_request?(request)
|
93
|
+
request.body.empty? || request.json? || request.form_data?
|
94
|
+
end
|
95
|
+
|
76
96
|
def validate_options(options)
|
77
97
|
raise Errors::ConfigurationError, Errors::API_KEY_ERROR if options[:api_key].nil?
|
78
98
|
|
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.
|
4
|
+
version: 1.0.0
|
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-
|
11
|
+
date: 2020-08-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -40,7 +40,9 @@ files:
|
|
40
40
|
- Rakefile
|
41
41
|
- bin/console
|
42
42
|
- bin/setup
|
43
|
+
- lib/content_type_helper.rb
|
43
44
|
- lib/http_request.rb
|
45
|
+
- lib/http_response.rb
|
44
46
|
- lib/readme/errors.rb
|
45
47
|
- lib/readme/filter.rb
|
46
48
|
- lib/readme/har/collection.rb
|