supergood 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,8 @@
1
+ require 'rudash'
2
+
3
+ module Supergood
4
+ module Utils
5
+ def hash_specified_keys
6
+ end
7
+ end
8
+ end
data/lib/supergood.rb ADDED
@@ -0,0 +1,6 @@
1
+ module Supergood
2
+ require_relative 'supergood/api'
3
+ require_relative 'supergood/logger'
4
+ require_relative 'supergood/client'
5
+ require_relative 'supergood/utils'
6
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Supergood
2
+ VERSION = '0.0.1'.freeze
3
+ end
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: []