ecoportal-api 0.10.2 → 0.10.3
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/CHANGELOG.md +9 -1
- data/lib/ecoportal/api/common/batch_operation.rb +19 -12
- data/lib/ecoportal/api/common/client/elastic_apm_integration.rb +111 -0
- data/lib/ecoportal/api/common/client/error/checks.rb +39 -0
- data/lib/ecoportal/api/common/client/error.rb +17 -0
- data/lib/ecoportal/api/common/client/time_out.rb +28 -0
- data/lib/ecoportal/api/common/client/with_retry.rb +86 -0
- data/lib/ecoportal/api/common/client.rb +45 -77
- data/lib/ecoportal/api/common.rb +0 -2
- data/lib/ecoportal/api/v1/people.rb +1 -1
- data/lib/ecoportal/api/version.rb +1 -1
- metadata +7 -4
- data/lib/ecoportal/api/common/elastic_apm_integration.rb +0 -112
- data/lib/ecoportal/api/common/time_out.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dde26458f8ba991809f2218f6a4594101350f861a811d1bf4c6b508b2194ef67
|
4
|
+
data.tar.gz: f9bd67ceaea38ec6ea82d9a53ae0651c36fb7f74e417b37029acd7d72fd00d97
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '09dd4b2cde2bd8f6c6689433c7472fedb6110d076603afcd8edc39f5275aed0296470e57900eaceb335dc875f31554d515925c6b1eae060786a055ac706f6a66'
|
7
|
+
data.tar.gz: 3363216e775f38601146617ee80abab13625163c46739f536d77f94c17247fcce139331cad8af02b4cbcc0848a352211a34c476d0d766082e18b5d4b222f43cf
|
data/CHANGELOG.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
|
5
|
-
## [0.10.
|
5
|
+
## [0.10.4] - 2024-10-xx
|
6
6
|
|
7
7
|
### Added
|
8
8
|
|
@@ -10,6 +10,14 @@ All notable changes to this project will be documented in this file.
|
|
10
10
|
|
11
11
|
### Fixed
|
12
12
|
|
13
|
+
## [0.10.3] - 2024-10-01
|
14
|
+
|
15
|
+
### Changed
|
16
|
+
|
17
|
+
- refactored Client
|
18
|
+
- initialized with `deep_logging` named argumnet
|
19
|
+
- previously `response_logging`
|
20
|
+
|
13
21
|
## [0.10.2] - 2024-09-27
|
14
22
|
|
15
23
|
### Added
|
@@ -4,11 +4,12 @@ module Ecoportal
|
|
4
4
|
class BatchOperation
|
5
5
|
include Common::DocHelpers
|
6
6
|
|
7
|
-
def initialize(base_path, wrapper, logger: nil)
|
8
|
-
@base_path
|
9
|
-
@wrapper
|
10
|
-
@operations
|
11
|
-
@logger
|
7
|
+
def initialize(base_path, wrapper, logger: nil, deep_logging: false)
|
8
|
+
@base_path = base_path
|
9
|
+
@wrapper = wrapper
|
10
|
+
@operations = []
|
11
|
+
@logger = logger
|
12
|
+
@deep_logging = deep_logging
|
12
13
|
end
|
13
14
|
|
14
15
|
def count
|
@@ -25,11 +26,12 @@ module Ecoportal
|
|
25
26
|
|
26
27
|
def process_response(response)
|
27
28
|
unless response.success?
|
28
|
-
|
29
|
-
|
29
|
+
msg = "Error: total failure in batch operation."
|
30
|
+
log(:debug) { msg }
|
31
|
+
raise msg
|
30
32
|
end
|
31
33
|
|
32
|
-
log(:
|
34
|
+
log(:debug) { "Processing batch responses" } if deep_logging?
|
33
35
|
|
34
36
|
body_data(response.body).each.with_index do |subresponse, idx|
|
35
37
|
status = subresponse["status"]
|
@@ -110,14 +112,19 @@ module Ecoportal
|
|
110
112
|
end
|
111
113
|
|
112
114
|
def log_batch_response(operation, response)
|
113
|
-
|
114
|
-
log(:info) { "Status #{response.status}" }
|
115
|
+
return unless deep_logging?
|
115
116
|
|
116
|
-
|
117
|
-
log(
|
117
|
+
log(:debug) { "BATCH #{operation[:method]} #{operation[:path]}" }
|
118
|
+
log(:debug) { "Status #{response.status}" }
|
119
|
+
log(:debug) { "Response: #{JSON.pretty_generate(response.body)}" }
|
120
|
+
end
|
121
|
+
|
122
|
+
def deep_logging?
|
123
|
+
@deep_logging
|
118
124
|
end
|
119
125
|
|
120
126
|
def log(level, &block)
|
127
|
+
puts "(#{level}) #{yield}"
|
121
128
|
@logger&.send(level, &block)
|
122
129
|
end
|
123
130
|
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'elastic-apm'
|
2
|
+
module Ecoportal
|
3
|
+
module API
|
4
|
+
module Common
|
5
|
+
class Client
|
6
|
+
module ElasticApmIntegration
|
7
|
+
include Ecoportal::API::Common::Client::Error::Checks
|
8
|
+
|
9
|
+
APM_SERVICE_NAME = 'ecoportal-api-gem'.freeze
|
10
|
+
|
11
|
+
# Log only errors that are only server's responsibility
|
12
|
+
def log_unexpected_server_error(response)
|
13
|
+
msg = "Expecting Ecoportal::API::Common::Response. Given: #{response.class}"
|
14
|
+
raise msg unless response.is_a?(Common::Response)
|
15
|
+
|
16
|
+
return unless elastic_apm_service
|
17
|
+
return unless unexpected_server_error_code?(response.status)
|
18
|
+
return unless ElasticAPM.running?
|
19
|
+
|
20
|
+
ElasticAPM.report(
|
21
|
+
Ecoportal::API::Common::Client::Error::UnexpectedServerError.new(
|
22
|
+
response.body,
|
23
|
+
code: response.status
|
24
|
+
)
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# finalizer to stop the agent
|
31
|
+
close_elastic_apm = proc do |_id|
|
32
|
+
next unless ElasticAPM.running?
|
33
|
+
|
34
|
+
puts "Stopping ElasticAPM service"
|
35
|
+
ElasticAPM.stop
|
36
|
+
rescue StandardError
|
37
|
+
# Silent
|
38
|
+
end
|
39
|
+
|
40
|
+
ObjectSpace.define_finalizer("ElasticAPM", close_elastic_apm)
|
41
|
+
|
42
|
+
def elastic_apm_service
|
43
|
+
return false if @disable_apm
|
44
|
+
|
45
|
+
ElasticAPM.start(**elastic_apm_options) unless ElasticAPM.running?
|
46
|
+
rescue StandardError => err
|
47
|
+
@disable_apm = true
|
48
|
+
puts "ElasticAPM services not available: #{err}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def elastic_apm_options
|
52
|
+
{
|
53
|
+
service_name: APM_SERVICE_NAME,
|
54
|
+
server_url: elastic_apm_url,
|
55
|
+
secret_token: elastic_apm_key,
|
56
|
+
environment: environment,
|
57
|
+
# http_compression: false,
|
58
|
+
transaction_sample_rate: 0.1,
|
59
|
+
transaction_max_spans: 100,
|
60
|
+
span_frames_min_duration: "5ms"
|
61
|
+
}.tap do |options|
|
62
|
+
# next unless false
|
63
|
+
|
64
|
+
options.merge!({
|
65
|
+
log_level: Logger::DEBUG,
|
66
|
+
log_path: File.join(__dir__, "elastic_apm.log")
|
67
|
+
})
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def elastic_apm_url
|
72
|
+
@elastic_apm_url ||= "https://".tap do |url|
|
73
|
+
url << elastic_apm_account_id.to_s
|
74
|
+
url << ".#{elastic_apm_base_url}"
|
75
|
+
url << ":#{elastic_apm_port}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def elastic_apm_key
|
80
|
+
@elastic_apm_key ||= ENV['ELASTIC_APM_KEY']
|
81
|
+
end
|
82
|
+
|
83
|
+
def elastic_apm_account_id
|
84
|
+
@elastic_apm_account_id ||= ENV['ELASTIC_APM_ACCOUNT_ID']
|
85
|
+
end
|
86
|
+
|
87
|
+
def elastic_apm_base_url
|
88
|
+
@elastic_apm_base_url ||= "apm.#{elastic_apm_region}.aws.cloud.es.io"
|
89
|
+
end
|
90
|
+
|
91
|
+
def elastic_apm_region
|
92
|
+
@elastic_apm_region ||= ENV['ELASTIC_APM_REGION'] || "ap-southeast-2"
|
93
|
+
end
|
94
|
+
|
95
|
+
def elastic_apm_port
|
96
|
+
@elastic_apm_port ||= ENV['ELASTIC_APM_PORT'] || "443"
|
97
|
+
end
|
98
|
+
|
99
|
+
def environment
|
100
|
+
@environment ||= "unknown".tap do |value|
|
101
|
+
next unless instance_variable_defined?(:@host)
|
102
|
+
next unless (env = @host.gsub(".ecoportal.com", ''))
|
103
|
+
|
104
|
+
value.clear << env
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Ecoportal
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
class Client
|
5
|
+
module Error
|
6
|
+
module Checks
|
7
|
+
private
|
8
|
+
|
9
|
+
def unexpected_server_error_code?(code)
|
10
|
+
return true unless code
|
11
|
+
return true if (code >= 500) && (code <= 599)
|
12
|
+
|
13
|
+
code <= 99
|
14
|
+
end
|
15
|
+
|
16
|
+
# Sometimes response body is wrong but status code
|
17
|
+
# doesn't reflect. Let it retry
|
18
|
+
def some_unexpected_error?(response)
|
19
|
+
return true if unexpected_server_error_code?(response.status)
|
20
|
+
|
21
|
+
unexpected_body?(response)
|
22
|
+
end
|
23
|
+
|
24
|
+
def unexpected_body?(response)
|
25
|
+
response.body.nil?.tap do |wrong|
|
26
|
+
next unless wrong
|
27
|
+
|
28
|
+
msg = "Received non json body in response "
|
29
|
+
msg << "(#{response.src_body.class}):\n "
|
30
|
+
msg << response.src_body
|
31
|
+
puts
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'ecoportal/api/common/client/error/checks'
|
2
|
+
|
3
|
+
module Ecoportal
|
4
|
+
module API
|
5
|
+
module Common
|
6
|
+
class Client
|
7
|
+
module Error
|
8
|
+
class UnexpectedServerError < StandardError
|
9
|
+
def initialize(msg, code:)
|
10
|
+
super("Code: #{code} -- Error: #{msg}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Ecoportal
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
class Client
|
5
|
+
module TimeOut
|
6
|
+
MIN_THROUGHPUT = 0.2 # people per second
|
7
|
+
MIN_SIZE = 10
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def min_throughput
|
12
|
+
self.class::MIN_THROUGHPUT
|
13
|
+
end
|
14
|
+
|
15
|
+
def min_size
|
16
|
+
self.class::MIN_SIZE
|
17
|
+
end
|
18
|
+
|
19
|
+
def timeout_for(count)
|
20
|
+
count = 1 unless count&.positive?
|
21
|
+
count = min_size if count < min_size
|
22
|
+
(count.ceil / min_throughput).ceil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Ecoportal
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
class Client
|
5
|
+
module WithRetry
|
6
|
+
DELAY_REQUEST_RETRY = 5
|
7
|
+
RETRY_ATTEMPTS = 5
|
8
|
+
HANDLED_CONNECTION_ERRORS = [
|
9
|
+
HTTP::ConnectionError,
|
10
|
+
IOError
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
include Ecoportal::API::Common::Client::ElasticApmIntegration
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
# Helper to ensure unexpected server errors do not bring
|
18
|
+
# client scripts immediately down
|
19
|
+
# @note it manages limited range of errors, the rest
|
20
|
+
# are not handled.
|
21
|
+
def with_retry(
|
22
|
+
attempts = retry_attemps,
|
23
|
+
delay = delay_request_retry,
|
24
|
+
error_safe: true,
|
25
|
+
&block
|
26
|
+
)
|
27
|
+
response = nil
|
28
|
+
|
29
|
+
attempts.times do |i|
|
30
|
+
remaining = attempts - i - 1
|
31
|
+
|
32
|
+
response = with_connection_error_handling(
|
33
|
+
remaining,
|
34
|
+
error_safe: error_safe,
|
35
|
+
callback: block
|
36
|
+
) do
|
37
|
+
block.call
|
38
|
+
end
|
39
|
+
|
40
|
+
return response unless some_unexpected_error?(response)
|
41
|
+
|
42
|
+
# handle server errors (5xx) & server bugs (i.e. empty body)
|
43
|
+
msg = "re-attempting (remaining: "
|
44
|
+
msg << "#{remaining} attempts out of #{attempts})"
|
45
|
+
log(:debug) { msg }
|
46
|
+
|
47
|
+
log_unexpected_server_error(response)
|
48
|
+
|
49
|
+
msg = "Got server error (#{response.status}): #{response.body}\n"
|
50
|
+
msg << "Going to retry (##{i} of #{attempts})"
|
51
|
+
log(:debug) { msg }
|
52
|
+
|
53
|
+
sleep(delay) if i < attempts
|
54
|
+
end
|
55
|
+
|
56
|
+
response
|
57
|
+
end
|
58
|
+
|
59
|
+
def with_connection_error_handling(remaining, callback:, error_safe: true)
|
60
|
+
yield
|
61
|
+
rescue *handled_connection_errors => err
|
62
|
+
raise unless error_safe && remaining.positive?
|
63
|
+
|
64
|
+
msg = "Got #{err.class}: #{err.message}"
|
65
|
+
log(:debug) { msg }
|
66
|
+
|
67
|
+
with_retry(remaining, error_safe: error_safe, &callback)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Add here other connection errors
|
71
|
+
def handled_connection_errors
|
72
|
+
self.class::HANDLED_CONNECTION_ERRORS
|
73
|
+
end
|
74
|
+
|
75
|
+
def retry_attemps
|
76
|
+
self.class::RETRY_ATTEMPTS
|
77
|
+
end
|
78
|
+
|
79
|
+
def delay_request_retry
|
80
|
+
self.class::DELAY_REQUEST_RETRY
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -1,4 +1,10 @@
|
|
1
1
|
require 'http'
|
2
|
+
|
3
|
+
require 'ecoportal/api/common/client/error'
|
4
|
+
require 'ecoportal/api/common/client/elastic_apm_integration'
|
5
|
+
require 'ecoportal/api/common/client/time_out'
|
6
|
+
require 'ecoportal/api/common/client/with_retry'
|
7
|
+
|
2
8
|
module Ecoportal
|
3
9
|
module API
|
4
10
|
module Common
|
@@ -16,9 +22,7 @@ module Ecoportal
|
|
16
22
|
# @attr_reader logger [Logger] the logger.
|
17
23
|
# @attr_reader host [String] the remote target server.
|
18
24
|
class Client
|
19
|
-
include
|
20
|
-
DELAY_REQUEST_RETRY = 5
|
21
|
-
RETRY_ATTEMPTS = 5
|
25
|
+
include WithRetry
|
22
26
|
|
23
27
|
attr_accessor :logger
|
24
28
|
attr_reader :host
|
@@ -28,40 +32,32 @@ module Ecoportal
|
|
28
32
|
# @param version [String] it is part of the base url and will determine the api version we query against.
|
29
33
|
# @param host [String] api server domain.
|
30
34
|
# @param logger [Logger] an object with `Logger` interface to generate logs.
|
31
|
-
# @param
|
35
|
+
# @param deep_logging [Boolean] whether or not batch responses should be logged
|
32
36
|
# @return [Client] an object that holds the configuration of the api connection.
|
33
|
-
def initialize(api_key:, version: "v1", host: "live.ecoportal.com", logger: nil,
|
34
|
-
@version
|
35
|
-
@api_key
|
36
|
-
@logger
|
37
|
-
@host
|
38
|
-
@
|
37
|
+
def initialize(api_key:, version: "v1", host: "live.ecoportal.com", logger: nil, deep_logging: false)
|
38
|
+
@version = version
|
39
|
+
@api_key = api_key
|
40
|
+
@logger = logger
|
41
|
+
@host = host
|
42
|
+
@deep_logging = deep_logging
|
39
43
|
|
40
44
|
if host.match(/^localhost|^127\.0\.0\.1/)
|
41
45
|
@base_uri = "http://#{host}/api/"
|
42
46
|
else
|
43
47
|
@base_uri = "https://#{host}/api/"
|
44
48
|
end
|
45
|
-
|
49
|
+
|
50
|
+
if deep_logging?
|
51
|
+
log(:debug) {
|
52
|
+
"#{version} client initialized pointing at #{host}"
|
53
|
+
}
|
54
|
+
end
|
46
55
|
|
47
56
|
return unless @api_key.nil? || @api_key.match(/\A\W*\z/)
|
48
57
|
|
49
58
|
log(:error) { "Api-key missing!" }
|
50
59
|
end
|
51
60
|
|
52
|
-
# Logger interface.
|
53
|
-
# @example:
|
54
|
-
# log(:info) {"General information on what's going on"}
|
55
|
-
# log(:warn) {"This is a warning that something is likely to have gone amiss"}
|
56
|
-
# log(:error) {"Something went wrong"}
|
57
|
-
# log(:fatal) {"An unrecoverable error has happend"}
|
58
|
-
# @param level [Symbol] the level that the message should be logged.
|
59
|
-
# @yield [] generates the message.
|
60
|
-
# @yieldreturn [String] the generated message.
|
61
|
-
def log(level, &block)
|
62
|
-
logger&.send(level, &block)
|
63
|
-
end
|
64
|
-
|
65
61
|
# Sends an http `GET` request against the api version using `path` to complete the base url,
|
66
62
|
# and adding the key_value pairs of `params` in the http _header_.
|
67
63
|
# @param path [String] the tail that completes the url of the request.
|
@@ -152,7 +148,7 @@ module Ecoportal
|
|
152
148
|
# @param path [String] the tail that completes the url of the request.
|
153
149
|
# @return [String] the final url.
|
154
150
|
def url_for(path)
|
155
|
-
@base_uri
|
151
|
+
"#{@base_uri}#{@version}#{path}"
|
156
152
|
end
|
157
153
|
|
158
154
|
private
|
@@ -161,71 +157,43 @@ module Ecoportal
|
|
161
157
|
raise "Expected block" unless block_given?
|
162
158
|
|
163
159
|
start_time = Time.now.to_f
|
164
|
-
|
165
|
-
|
160
|
+
|
161
|
+
if deep_logging?
|
162
|
+
log(:debug) { "#{method} #{url_for(path)}" }
|
163
|
+
log(:debug) { "Data: #{JSON.pretty_generate(data)}" }
|
164
|
+
end
|
166
165
|
|
167
166
|
with_retry(&block).tap do |result|
|
167
|
+
next unless deep_logging?
|
168
|
+
|
168
169
|
end_time = Time.now.to_f
|
169
|
-
|
170
|
+
|
171
|
+
log(:debug) {
|
170
172
|
"Took %.2fs, Status #{result.status}" % (end_time - start_time) # rubocop:disable Style/FormatString
|
171
173
|
}
|
172
174
|
|
173
|
-
|
174
|
-
|
175
|
-
log(result.success?? :debug : :warn) {
|
175
|
+
log(:debug) {
|
176
176
|
"Response: #{JSON.pretty_generate(result.body)}"
|
177
177
|
}
|
178
178
|
end
|
179
179
|
end
|
180
180
|
|
181
|
-
|
182
|
-
|
183
|
-
response = nil
|
184
|
-
attempts.times do |i|
|
185
|
-
remaining = attempts - i - 1
|
186
|
-
|
187
|
-
begin
|
188
|
-
response = block.call
|
189
|
-
rescue HTTP::ConnectionError => e
|
190
|
-
raise unless error_safe && remaining.positive?
|
191
|
-
log(:error) { "Got connection error: #{e.message}" }
|
192
|
-
response = with_retry(remaining, error_safe: error_safe, &block)
|
193
|
-
rescue IOError => e
|
194
|
-
raise unless error_safe && remaining.positive?
|
195
|
-
log(:error) { "Got IO error: #{e.message}" }
|
196
|
-
response = with_retry(remaining, error_safe: error_safe, &block)
|
197
|
-
end
|
198
|
-
|
199
|
-
return response unless some_unexpected_error?(response)
|
200
|
-
|
201
|
-
puts "re-attempting (remaining: #{remaining} attempts out of #{attempts})"
|
202
|
-
|
203
|
-
log_unexpected_server_error(response)
|
204
|
-
|
205
|
-
msg = "Got server error (#{response.status}): #{response.body}\n"
|
206
|
-
msg += "Going to retry (#{i} out of #{attempts})"
|
207
|
-
log(:error) { msg }
|
208
|
-
|
209
|
-
sleep(delay) if i < attempts
|
210
|
-
end
|
211
|
-
response
|
212
|
-
end
|
213
|
-
|
214
|
-
# Sometimes response body is wrong but status code
|
215
|
-
# doesn't reflect. Let it retry
|
216
|
-
def some_unexpected_error?(response)
|
217
|
-
unexpected_server_error?(response.status) ||
|
218
|
-
unexpected_body?(response)
|
181
|
+
def deep_logging?
|
182
|
+
@deep_logging
|
219
183
|
end
|
220
184
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
185
|
+
# Logger interface.
|
186
|
+
# @example:
|
187
|
+
# log(:info) {"General information on what's going on"}
|
188
|
+
# log(:warn) {"This is a warning that something is likely to have gone amiss"}
|
189
|
+
# log(:error) {"Something went wrong"}
|
190
|
+
# log(:fatal) {"An unrecoverable error has happend"}
|
191
|
+
# @param level [Symbol] the level that the message should be logged.
|
192
|
+
# @yield [] generates the message.
|
193
|
+
# @yieldreturn [String] the generated message.
|
194
|
+
def log(level, &block)
|
195
|
+
puts "(#{level}) #{yield}"
|
196
|
+
logger&.send(level, &block)
|
229
197
|
end
|
230
198
|
end
|
231
199
|
end
|
data/lib/ecoportal/api/common.rb
CHANGED
@@ -10,8 +10,6 @@ require 'ecoportal/api/common/hash_diff'
|
|
10
10
|
require 'ecoportal/api/common/base_model'
|
11
11
|
require 'ecoportal/api/common/doc_helpers'
|
12
12
|
require 'ecoportal/api/common/logging'
|
13
|
-
require 'ecoportal/api/common/elastic_apm_integration'
|
14
|
-
require 'ecoportal/api/common/time_out'
|
15
13
|
require 'ecoportal/api/common/client'
|
16
14
|
require 'ecoportal/api/common/response'
|
17
15
|
require 'ecoportal/api/common/wrapped_response'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ecoportal-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.10.
|
4
|
+
version: 0.10.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tapio Saarinen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-09-
|
11
|
+
date: 2024-09-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry
|
@@ -209,12 +209,15 @@ files:
|
|
209
209
|
- lib/ecoportal/api/common/batch_operation.rb
|
210
210
|
- lib/ecoportal/api/common/batch_response.rb
|
211
211
|
- lib/ecoportal/api/common/client.rb
|
212
|
+
- lib/ecoportal/api/common/client/elastic_apm_integration.rb
|
213
|
+
- lib/ecoportal/api/common/client/error.rb
|
214
|
+
- lib/ecoportal/api/common/client/error/checks.rb
|
215
|
+
- lib/ecoportal/api/common/client/time_out.rb
|
216
|
+
- lib/ecoportal/api/common/client/with_retry.rb
|
212
217
|
- lib/ecoportal/api/common/doc_helpers.rb
|
213
|
-
- lib/ecoportal/api/common/elastic_apm_integration.rb
|
214
218
|
- lib/ecoportal/api/common/hash_diff.rb
|
215
219
|
- lib/ecoportal/api/common/logging.rb
|
216
220
|
- lib/ecoportal/api/common/response.rb
|
217
|
-
- lib/ecoportal/api/common/time_out.rb
|
218
221
|
- lib/ecoportal/api/common/wrapped_response.rb
|
219
222
|
- lib/ecoportal/api/errors.rb
|
220
223
|
- lib/ecoportal/api/errors/base.rb
|
@@ -1,112 +0,0 @@
|
|
1
|
-
require 'elastic-apm'
|
2
|
-
module Ecoportal
|
3
|
-
module API
|
4
|
-
module Common
|
5
|
-
module ElasticApmIntegration
|
6
|
-
|
7
|
-
class UnexpectedServerError < StandardError
|
8
|
-
def initialize(code, msg)
|
9
|
-
super("Code: #{code} -- Error: #{msg}")
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
APM_SERVICE_NAME = 'ecoportal-api-gem'
|
14
|
-
|
15
|
-
# Log only errors that are only server's responsibility
|
16
|
-
def log_unexpected_server_error(response)
|
17
|
-
raise "Expecting Ecoportal::API::Common::Response. Given: #{response.class}" unless response.is_a?(Common::Response)
|
18
|
-
return nil unless elastic_apm_service
|
19
|
-
return nil unless unexpected_server_error?(response.status)
|
20
|
-
if ElasticAPM.running?
|
21
|
-
ElasticAPM.report(UnexpectedServerError.new(response.status, response.body))
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def unexpected_server_error?(code)
|
28
|
-
!code || ((code >= 500) && (code <= 599)) || (code <= 99)
|
29
|
-
end
|
30
|
-
|
31
|
-
# finalizer to stop the agent
|
32
|
-
close_elastic_apm = Proc.new do |id|
|
33
|
-
begin
|
34
|
-
if ElasticAPM.running?
|
35
|
-
puts "Stopping ElasticAPM service"
|
36
|
-
ElasticAPM.stop
|
37
|
-
end
|
38
|
-
rescue StandardError => e
|
39
|
-
# Silent
|
40
|
-
end
|
41
|
-
end
|
42
|
-
ObjectSpace.define_finalizer("ElasticAPM", close_elastic_apm)
|
43
|
-
|
44
|
-
def elastic_apm_service
|
45
|
-
return false if @disable_apm
|
46
|
-
begin
|
47
|
-
ElasticAPM.start(**elastic_apm_options) unless ElasticAPM.running?
|
48
|
-
rescue StandardError => e
|
49
|
-
@disable_apm = true
|
50
|
-
puts "ElasticAPM services not available: #{e}"
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def elastic_apm_options
|
55
|
-
{
|
56
|
-
service_name: APM_SERVICE_NAME,
|
57
|
-
server_url: elastic_apm_url,
|
58
|
-
secret_token: elastic_apm_key,
|
59
|
-
environment: environment,
|
60
|
-
#http_compression: false,
|
61
|
-
transaction_sample_rate: 0.1,
|
62
|
-
transaction_max_spans: 100,
|
63
|
-
span_frames_min_duration: "5ms"
|
64
|
-
}.tap do |options|
|
65
|
-
options.merge!({
|
66
|
-
log_level: Logger::DEBUG,
|
67
|
-
log_path: File.join(__dir__, "elastic_apm.log")
|
68
|
-
}) if false
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def elastic_apm_url
|
73
|
-
@elastic_apm_url ||= "https://".tap do |url|
|
74
|
-
url << "#{elastic_apm_account_id}"
|
75
|
-
url << ".#{elastic_apm_base_url}"
|
76
|
-
url << ":#{elastic_apm_port}"
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def elastic_apm_key
|
81
|
-
@elastic_apm_key ||= ENV['ELASTIC_APM_KEY']
|
82
|
-
end
|
83
|
-
|
84
|
-
def elastic_apm_account_id
|
85
|
-
@elastic_apm_account_id ||= ENV['ELASTIC_APM_ACCOUNT_ID']
|
86
|
-
end
|
87
|
-
|
88
|
-
def elastic_apm_base_url
|
89
|
-
@elastic_apm_base_url ||= "apm.#{elastic_apm_region}.aws.cloud.es.io"
|
90
|
-
end
|
91
|
-
|
92
|
-
def elastic_apm_region
|
93
|
-
@elastic_apm_region ||= ENV['ELASTIC_APM_REGION'] || "ap-southeast-2"
|
94
|
-
end
|
95
|
-
|
96
|
-
|
97
|
-
def elastic_apm_port
|
98
|
-
@elastic_apm_port ||= ENV['ELASTIC_APM_PORT'] || "443"
|
99
|
-
end
|
100
|
-
|
101
|
-
def environment
|
102
|
-
@environment ||= "unknown".tap do |value|
|
103
|
-
if instance_variable_defined?(:@host) && env = @host.gsub(".ecoportal.com", '')
|
104
|
-
value.clear << env
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
module Ecoportal
|
2
|
-
module API
|
3
|
-
module Common
|
4
|
-
module TimeOut
|
5
|
-
MIN_THROUGHPUT = 0.2 # people per second
|
6
|
-
MIN_SIZE = 10
|
7
|
-
|
8
|
-
private
|
9
|
-
|
10
|
-
def min_throughput
|
11
|
-
self.class::MIN_THROUGHPUT
|
12
|
-
end
|
13
|
-
|
14
|
-
def min_size
|
15
|
-
self.class::MIN_SIZE
|
16
|
-
end
|
17
|
-
|
18
|
-
def timeout_for(count)
|
19
|
-
count = 1 unless count&.positive?
|
20
|
-
count = min_size if count < min_size
|
21
|
-
(count.ceil / min_throughput).ceil
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|