supergood 0.0.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 +7 -0
- data/lib/supergood/api.rb +48 -0
- data/lib/supergood/client.rb +205 -0
- data/lib/supergood/logger.rb +31 -0
- data/lib/supergood/utils.rb +8 -0
- data/lib/supergood.rb +6 -0
- data/lib/version.rb +3 -0
- metadata +49 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 665ca96dbcbfec2b928ff9dd1828ad464d63b23048346ee0324cbeb2b2f22977
|
4
|
+
data.tar.gz: e29262ab2a84f91991bb912223acf3d23e2e9332cbd0a33d6ec03db6b8388be0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a1b3f9b9f987bc19cf9e596126830576374cd75d3eb5179d1a9615f137191440bba6999a376a207847626ad2ee1041662fd2ca45be5c9d8ce88aff1c093cb28d
|
7
|
+
data.tar.gz: e96124b5501e72db95ac4e6c9e76c69896f6c8d0542b33c085d060d43e73d9da16f998fcd0a880dfc5d847f39b8d30c27b81963442965d26e3a86649a629063c
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module Supergood
|
4
|
+
class Api
|
5
|
+
DEFAULT_SUPERGOOD_BASE_URL = 'https://dashboard.supergood.ai'
|
6
|
+
DEFAULT_SUPERGOOD_CONFIG = {
|
7
|
+
flush_interval: 1,
|
8
|
+
event_sink_endpoint: DEFAULT_SUPERGOOD_BASE_URL + '/api/events',
|
9
|
+
error_sink_endpoint: DEFAULT_SUPERGOOD_BASE_URL + '/api/errors',
|
10
|
+
keys_to_hash: ['request.body', 'response.body'],
|
11
|
+
ignored_domains: []
|
12
|
+
}
|
13
|
+
|
14
|
+
def initialize(header_options, base_url)
|
15
|
+
@header_options = header_options
|
16
|
+
@config_fetch_url = base_url + '/api/config'
|
17
|
+
end
|
18
|
+
|
19
|
+
def log
|
20
|
+
@log
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_logger(logger)
|
24
|
+
@log = logger
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_event_sink_url(url)
|
28
|
+
@event_sink_url = url
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_error_sink_url(url)
|
32
|
+
@error_sink_url = url
|
33
|
+
end
|
34
|
+
|
35
|
+
def post_events(payload)
|
36
|
+
Faraday.post(@event_sink_url, payload, @header_options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def post_errors(payload)
|
40
|
+
Faraday.post(@event_sink_url, payload, @header_options)
|
41
|
+
end
|
42
|
+
|
43
|
+
def fetch_config
|
44
|
+
# Faraday.get(config_fetch_url, @header_options)
|
45
|
+
DEFAULT_SUPERGOOD_CONFIG
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'webmock'
|
2
|
+
require 'json'
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
require 'securerandom'
|
6
|
+
require 'dotenv'
|
7
|
+
require 'base64'
|
8
|
+
|
9
|
+
Dotenv.load
|
10
|
+
|
11
|
+
module Supergood
|
12
|
+
|
13
|
+
DEFAULT_SUPERGOOD_BASE_URL = 'https://dashboard.supergood.ai'
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def init(supergood_client_id=nil, supergood_client_secret=nil, base_url=nil)
|
17
|
+
|
18
|
+
supergood_client_id = supergood_client_id || ENV['SUPERGOOD_CLIENT_ID']
|
19
|
+
supergood_client_secret = supergood_client_secret || ENV['SUPERGOOD_CLIENT_SECRET']
|
20
|
+
base_url = base_url || ENV['SUPERGOOD_BASE_URL'] || DEFAULT_SUPERGOOD_BASE_URL
|
21
|
+
puts "Initializing Supergood with client_id: #{supergood_client_id}, client_secret: #{supergood_client_secret}, base_url: #{base_url}"
|
22
|
+
header_options = {
|
23
|
+
'headers' => {
|
24
|
+
'Content-Type' => 'application/json',
|
25
|
+
'Authorization' => 'Basic ' + Base64.encode64(supergood_client_id + ':' + supergood_client_secret)
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
@api = Supergood::Api.new(header_options, base_url)
|
30
|
+
@logger = Supergood::Logger.new(@api, header_options, base_url)
|
31
|
+
|
32
|
+
config = api.fetch_config()
|
33
|
+
|
34
|
+
@ignored_domains = config[:ignored_domains]
|
35
|
+
@keys_to_hash = config[:keys_to_hash]
|
36
|
+
|
37
|
+
api.set_error_sink_url(base_url + config[:error_sink_endpoint])
|
38
|
+
api.set_event_sink_url(base_url + config[:event_sink_endpoint])
|
39
|
+
api.set_logger(@logger)
|
40
|
+
|
41
|
+
@request_cache = {}
|
42
|
+
@response_cache = {}
|
43
|
+
|
44
|
+
set_interval(config[:flush_interval]) { flush_cache }
|
45
|
+
end
|
46
|
+
|
47
|
+
def log
|
48
|
+
@logger
|
49
|
+
end
|
50
|
+
|
51
|
+
def api
|
52
|
+
@api
|
53
|
+
end
|
54
|
+
|
55
|
+
def flush_cache(force = false)
|
56
|
+
# If there's notthing in the response cache, and we're not forcing a flush, then return
|
57
|
+
if @response_cache.empty? && !force
|
58
|
+
return
|
59
|
+
elsif force && @response_cache.empty? && @request_cache.empty?
|
60
|
+
return
|
61
|
+
end
|
62
|
+
|
63
|
+
data = @response_cache.values
|
64
|
+
|
65
|
+
if force
|
66
|
+
data += @request_cache.values
|
67
|
+
end
|
68
|
+
|
69
|
+
begin
|
70
|
+
log.debug(data)
|
71
|
+
# api.post_events(data)
|
72
|
+
rescue => e
|
73
|
+
#TODO Add error posting to Supergood
|
74
|
+
puts "Error posting events: #{e}"
|
75
|
+
api.post_errors(e)
|
76
|
+
ensure
|
77
|
+
@response_cache.clear
|
78
|
+
@request_cache.clear if force
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
def set_interval(delay)
|
84
|
+
Thread.new do
|
85
|
+
loop do
|
86
|
+
sleep delay
|
87
|
+
yield # call passed block
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.intercept(*args, &block)
|
93
|
+
instance.intercept(*args, &block)
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.instance
|
97
|
+
@instance ||= Supergood.new
|
98
|
+
end
|
99
|
+
|
100
|
+
def intercept(http, request, request_body)
|
101
|
+
request_id = SecureRandom.uuid
|
102
|
+
start_time = Time.now
|
103
|
+
response = yield
|
104
|
+
ensure
|
105
|
+
if log_event?(http, request)
|
106
|
+
time = ((Time.now - start_time) * 1000)
|
107
|
+
url_payload = parse_url(http, request)
|
108
|
+
@request_cache[request_id] = {
|
109
|
+
request: {
|
110
|
+
id: request_id,
|
111
|
+
method: request.method,
|
112
|
+
url: url_payload[:url],
|
113
|
+
protocol: url_payload[:protocol],
|
114
|
+
domain: url_payload[:domain],
|
115
|
+
path: url_payload[:path],
|
116
|
+
search: url_payload[:search],
|
117
|
+
body: hash_specified_keys(request.body),
|
118
|
+
header: hash_specified_keys(get_header(request)),
|
119
|
+
}
|
120
|
+
}
|
121
|
+
if defined?(response) && response
|
122
|
+
request_payload = @request_cache[request_id]
|
123
|
+
@response_cache[request_id] = {
|
124
|
+
request: request_payload,
|
125
|
+
response: {
|
126
|
+
status: response.code,
|
127
|
+
status_text: response.message,
|
128
|
+
header: hash_specified_keys(get_header(response)),
|
129
|
+
body: hash_specified_keys(response.body),
|
130
|
+
}
|
131
|
+
}
|
132
|
+
@request_cache.delete(request_id)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def log_event?(http, request)
|
138
|
+
!ignored?(http, request) && (http.started? || webmock?(http, request))
|
139
|
+
end
|
140
|
+
|
141
|
+
def ignored?(http, request)
|
142
|
+
url = request_url(http, request)
|
143
|
+
@ignored_domains.any? do |pattern|
|
144
|
+
url =~ pattern
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def webmock?(http, request)
|
149
|
+
return false unless defined?(::WebMock)
|
150
|
+
uri = request_uri_as_string(http, request)
|
151
|
+
method = request.method.downcase.to_sym
|
152
|
+
signature = WebMock::RequestSignature.new(method, uri)
|
153
|
+
::WebMock.registered_request?(signature)
|
154
|
+
end
|
155
|
+
|
156
|
+
# TODO: Hash keys, move to Utils?
|
157
|
+
def hash_specified_keys(body_or_header)
|
158
|
+
body_or_header
|
159
|
+
end
|
160
|
+
|
161
|
+
def get_header(request_or_response)
|
162
|
+
header = {}
|
163
|
+
request_or_response.each_header do |k,v|
|
164
|
+
header[k] = v
|
165
|
+
end
|
166
|
+
header
|
167
|
+
end
|
168
|
+
|
169
|
+
def parse_url(http, request)
|
170
|
+
url = request_url(http, request)
|
171
|
+
uri = URI.parse(url)
|
172
|
+
{
|
173
|
+
url: url,
|
174
|
+
protocol: uri.scheme,
|
175
|
+
domain: uri.host,
|
176
|
+
path: uri.path,
|
177
|
+
search: uri.query,
|
178
|
+
}
|
179
|
+
end
|
180
|
+
|
181
|
+
def request_url(http, request)
|
182
|
+
URI::DEFAULT_PARSER.unescape("http#{"s" if http.use_ssl?}://#{http.address}:#{http.port}#{request.path}")
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
# Attach library to Net::HTTP and WebMock
|
189
|
+
block = lambda do |a|
|
190
|
+
# raise instance_methods.inspect
|
191
|
+
alias request_without_net_http_logger request
|
192
|
+
def request(request, body = nil, &block)
|
193
|
+
Supergood.intercept(self, request, body) do
|
194
|
+
request_without_net_http_logger(request, body, &block)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
if defined?(::WebMock)
|
200
|
+
klass = WebMock::HttpLibAdapters::NetHttpAdapter.instance_variable_get("@webMockNetHTTP")
|
201
|
+
# raise klass.instance_methods.inspect
|
202
|
+
klass.class_eval(&block)
|
203
|
+
end
|
204
|
+
|
205
|
+
Net::HTTP.class_eval(&block)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'dotenv'
|
3
|
+
|
4
|
+
Dotenv.load
|
5
|
+
|
6
|
+
module Supergood
|
7
|
+
class Logger < Logger
|
8
|
+
def initialize(post_errors, config, header_options)
|
9
|
+
super(STDOUT)
|
10
|
+
@post_errors = post_errors
|
11
|
+
@config = config
|
12
|
+
@header_options = header_options
|
13
|
+
end
|
14
|
+
|
15
|
+
def post_errors
|
16
|
+
@post_errors
|
17
|
+
end
|
18
|
+
|
19
|
+
def warn(payload, error, msg)
|
20
|
+
super
|
21
|
+
post_errors({ error: error, message: msg, payload: payload })
|
22
|
+
end
|
23
|
+
|
24
|
+
def debug(payload)
|
25
|
+
if(ENV['SUPERGOOD_LOG_LEVEL'] == 'debug')
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
data/lib/supergood.rb
ADDED
data/lib/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: supergood
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alex Klarfeld
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-03-13 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email: alex@supergood.ai
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/supergood.rb
|
20
|
+
- lib/supergood/api.rb
|
21
|
+
- lib/supergood/client.rb
|
22
|
+
- lib/supergood/logger.rb
|
23
|
+
- lib/supergood/utils.rb
|
24
|
+
- lib/version.rb
|
25
|
+
homepage: https://supergood.ai
|
26
|
+
licenses:
|
27
|
+
- Business Source License 1.1
|
28
|
+
metadata:
|
29
|
+
source_code_uri: https://github.com/supergoodsystems/supergood-rb
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: 2.1.0
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubygems_version: 3.0.3.1
|
46
|
+
signing_key:
|
47
|
+
specification_version: 4
|
48
|
+
summary: Supergood - API monitoring
|
49
|
+
test_files: []
|