posthog-ruby 3.14.2 → 3.15.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/lib/posthog/client.rb +8 -2
- data/lib/posthog/defaults.rb +4 -0
- data/lib/posthog/feature_flags.rb +33 -10
- data/lib/posthog/send_worker.rb +6 -1
- data/lib/posthog/transport.rb +25 -3
- data/lib/posthog/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ab01f90a9fbe05af027e92831b58e906eb38b30914230fd83a35cab28da40fcd
|
|
4
|
+
data.tar.gz: cf06c8facfe324712e096cedfeeef90c755705bcce79384cda2d7b5f4d675cf0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6825662770b6a96fce094e2c46619fe142144ef8098db49c6a67b4f1faf54d731b65c8767f985413c91bf097767db84648fe2b2ba938ca4f788a0201ffa6002e
|
|
7
|
+
data.tar.gz: 8501399ef589da90e3e7c65629df24d99012240c718874a506fede7833225316a1c97881a8eca1df06a831eace6e9090e3b052686c7656c8cd9a3a1815e3a6b7
|
data/lib/posthog/client.rb
CHANGED
|
@@ -67,12 +67,16 @@ module PostHog
|
|
|
67
67
|
# in seconds. Defaults to 30.
|
|
68
68
|
# @option opts [Integer] :feature_flag_request_timeout_seconds How long to wait for feature flag evaluation,
|
|
69
69
|
# in seconds. Defaults to 3.
|
|
70
|
+
# @option opts [Integer] :feature_flag_request_max_retries How many times to retry a flag request after a
|
|
71
|
+
# transient network error. Each retry sleeps on the calling thread before retrying, so this adds to
|
|
72
|
+
# worst-case latency. Defaults to 1. Set to 0 to disable retrying.
|
|
70
73
|
# @option opts [Proc] :before_send A callback that receives the event hash and should return either a modified
|
|
71
74
|
# hash to be sent to PostHog or nil to prevent the event from being sent. e.g. `before_send: ->(event) { event }`.
|
|
72
75
|
# @option opts [Boolean] :disable_singleton_warning +true+ to suppress the warning when multiple clients share
|
|
73
76
|
# the same API key. Use only when you intentionally need multiple clients. Defaults to +false+.
|
|
74
77
|
# @option opts [Boolean] :skip_ssl_verification +true+ to disable SSL certificate verification for requests.
|
|
75
78
|
# Intended only for local development or custom deployments.
|
|
79
|
+
# @option opts [Boolean] :compress_request Set to +false+ to disable gzip compression for batch uploads.
|
|
76
80
|
# @option opts [Object] :flag_definition_cache_provider An object implementing the {FlagDefinitionCacheProvider}
|
|
77
81
|
# interface for distributed flag definition caching.
|
|
78
82
|
# @option opts [Boolean] :is_server +true+ to stamp captured events with `$is_server => true` so PostHog
|
|
@@ -106,7 +110,8 @@ module PostHog
|
|
|
106
110
|
@transport = Transport.new(
|
|
107
111
|
api_host: opts[:host],
|
|
108
112
|
skip_ssl_verification: opts[:skip_ssl_verification],
|
|
109
|
-
retries: 3
|
|
113
|
+
retries: 3,
|
|
114
|
+
compress_request: opts[:compress_request]
|
|
110
115
|
)
|
|
111
116
|
@sync_lock = Mutex.new
|
|
112
117
|
end
|
|
@@ -140,7 +145,8 @@ module PostHog
|
|
|
140
145
|
opts[:host],
|
|
141
146
|
opts[:feature_flag_request_timeout_seconds] || Defaults::FeatureFlags::FLAG_REQUEST_TIMEOUT_SECONDS,
|
|
142
147
|
opts[:on_error],
|
|
143
|
-
flag_definition_cache_provider: opts[:flag_definition_cache_provider]
|
|
148
|
+
flag_definition_cache_provider: opts[:flag_definition_cache_provider],
|
|
149
|
+
feature_flag_request_max_retries: opts[:feature_flag_request_max_retries]
|
|
144
150
|
)
|
|
145
151
|
end
|
|
146
152
|
|
data/lib/posthog/defaults.rb
CHANGED
|
@@ -19,6 +19,10 @@ module PostHog
|
|
|
19
19
|
|
|
20
20
|
module FeatureFlags
|
|
21
21
|
FLAG_REQUEST_TIMEOUT_SECONDS = 3
|
|
22
|
+
# Number of retries for a flag request after a transient network error.
|
|
23
|
+
# Flag requests are stateless and cause no server-side mutation, so
|
|
24
|
+
# retrying is safe.
|
|
25
|
+
FLAG_REQUEST_MAX_RETRIES = 1
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
module Queue
|
|
@@ -5,6 +5,8 @@ require 'net/http'
|
|
|
5
5
|
require 'json'
|
|
6
6
|
require 'posthog/version'
|
|
7
7
|
require 'posthog/logging'
|
|
8
|
+
require 'posthog/defaults'
|
|
9
|
+
require 'posthog/backoff_policy'
|
|
8
10
|
require 'posthog/feature_flag'
|
|
9
11
|
require 'posthog/flag_definition_cache'
|
|
10
12
|
require 'digest'
|
|
@@ -34,6 +36,8 @@ module PostHog
|
|
|
34
36
|
# @param feature_flag_request_timeout_seconds [Integer] Timeout for feature flag requests.
|
|
35
37
|
# @param on_error [Proc, nil] Callback invoked as `on_error.call(status, error)`.
|
|
36
38
|
# @param flag_definition_cache_provider [Object, nil] Optional {FlagDefinitionCacheProvider} implementation.
|
|
39
|
+
# @param feature_flag_request_max_retries [Integer, nil] Retries after a transient network error on a flag
|
|
40
|
+
# request. Defaults to {Defaults::FeatureFlags::FLAG_REQUEST_MAX_RETRIES}. Set to 0 to disable retrying.
|
|
37
41
|
def initialize(
|
|
38
42
|
polling_interval,
|
|
39
43
|
personal_api_key,
|
|
@@ -41,7 +45,8 @@ module PostHog
|
|
|
41
45
|
host,
|
|
42
46
|
feature_flag_request_timeout_seconds,
|
|
43
47
|
on_error = nil,
|
|
44
|
-
flag_definition_cache_provider: nil
|
|
48
|
+
flag_definition_cache_provider: nil,
|
|
49
|
+
feature_flag_request_max_retries: nil
|
|
45
50
|
)
|
|
46
51
|
@polling_interval = polling_interval || 30
|
|
47
52
|
@personal_api_key = personal_api_key
|
|
@@ -53,6 +58,8 @@ module PostHog
|
|
|
53
58
|
@loaded_flags_successfully_once = Concurrent::AtomicBoolean.new
|
|
54
59
|
@feature_flags_by_key = nil
|
|
55
60
|
@feature_flag_request_timeout_seconds = feature_flag_request_timeout_seconds
|
|
61
|
+
@feature_flag_request_max_retries =
|
|
62
|
+
feature_flag_request_max_retries || Defaults::FeatureFlags::FLAG_REQUEST_MAX_RETRIES
|
|
56
63
|
@on_error = on_error || proc { |status, error| }
|
|
57
64
|
@quota_limited = Concurrent::AtomicBoolean.new(false)
|
|
58
65
|
@flags_etag = Concurrent::AtomicReference.new(nil)
|
|
@@ -1240,12 +1247,24 @@ module PostHog
|
|
|
1240
1247
|
_request(uri, req, @feature_flag_request_timeout_seconds)
|
|
1241
1248
|
end
|
|
1242
1249
|
|
|
1243
|
-
#
|
|
1250
|
+
# Transient network errors that are safe to retry. Flag requests are
|
|
1251
|
+
# retry-safe (stateless reads and evaluations, no server-side mutation), so
|
|
1252
|
+
# a one-off blip (TCP retransmit, TLS jitter, an edge/proxy hiccup) should
|
|
1253
|
+
# be absorbed by a retry rather than surfaced to the caller.
|
|
1254
|
+
RETRYABLE_REQUEST_ERRORS = [
|
|
1255
|
+
Timeout::Error, # covers Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout
|
|
1256
|
+
Errno::ECONNRESET,
|
|
1257
|
+
EOFError
|
|
1258
|
+
].freeze
|
|
1259
|
+
|
|
1244
1260
|
def _request(uri, request_object, timeout = nil, include_etag: false)
|
|
1245
1261
|
request_object['User-Agent'] = "posthog-ruby/#{PostHog::VERSION}"
|
|
1246
1262
|
request_timeout = timeout || 10
|
|
1263
|
+
backoff_policy = nil
|
|
1264
|
+
attempts = 0
|
|
1247
1265
|
|
|
1248
1266
|
begin
|
|
1267
|
+
attempts += 1
|
|
1249
1268
|
Net::HTTP.start(
|
|
1250
1269
|
uri.hostname,
|
|
1251
1270
|
uri.port,
|
|
@@ -1279,20 +1298,24 @@ module PostHog
|
|
|
1279
1298
|
return error_response
|
|
1280
1299
|
end
|
|
1281
1300
|
end
|
|
1282
|
-
rescue
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1301
|
+
rescue *RETRYABLE_REQUEST_ERRORS => e
|
|
1302
|
+
if attempts <= @feature_flag_request_max_retries
|
|
1303
|
+
backoff_policy ||= BackoffPolicy.new
|
|
1304
|
+
interval = backoff_policy.next_interval.to_f / 1000
|
|
1305
|
+
logger.debug("Retrying request to #{_mask_tokens_in_url(uri.to_s)} after #{e.class} (attempt #{attempts})")
|
|
1306
|
+
sleep(interval)
|
|
1307
|
+
retry
|
|
1308
|
+
end
|
|
1309
|
+
logger.debug("Unable to complete request to #{_mask_tokens_in_url(uri.to_s)}")
|
|
1310
|
+
raise
|
|
1311
|
+
rescue Errno::EINVAL,
|
|
1286
1312
|
Net::HTTPBadResponse,
|
|
1287
1313
|
Net::HTTPHeaderSyntaxError,
|
|
1288
|
-
Net::ReadTimeout,
|
|
1289
|
-
Net::WriteTimeout,
|
|
1290
1314
|
Net::ProtocolError
|
|
1291
|
-
logger.debug("Unable to complete request to #{uri}")
|
|
1315
|
+
logger.debug("Unable to complete request to #{_mask_tokens_in_url(uri.to_s)}")
|
|
1292
1316
|
raise
|
|
1293
1317
|
end
|
|
1294
1318
|
end
|
|
1295
|
-
# rubocop:enable Lint/ShadowedException
|
|
1296
1319
|
|
|
1297
1320
|
def _mask_tokens_in_url(url)
|
|
1298
1321
|
url.gsub(/token=([^&]{10})[^&]*/, 'token=\1...')
|
data/lib/posthog/send_worker.rb
CHANGED
|
@@ -27,6 +27,7 @@ module PostHog
|
|
|
27
27
|
# @option options [Proc] :on_error Callback invoked as `on_error.call(status, error)`.
|
|
28
28
|
# @option options [String] :host PostHog API host URL.
|
|
29
29
|
# @option options [Boolean] :skip_ssl_verification Disable SSL certificate verification.
|
|
30
|
+
# @option options [Boolean] :compress_request Set to +false+ to disable gzip batch request bodies.
|
|
30
31
|
def initialize(queue, api_key, options = {})
|
|
31
32
|
symbolize_keys! options
|
|
32
33
|
@queue = queue
|
|
@@ -42,7 +43,11 @@ module PostHog
|
|
|
42
43
|
@flush_requested = false
|
|
43
44
|
@shutdown = false
|
|
44
45
|
@pid = Process.pid
|
|
45
|
-
@transport_options = {
|
|
46
|
+
@transport_options = {
|
|
47
|
+
api_host: options[:host],
|
|
48
|
+
skip_ssl_verification: options[:skip_ssl_verification],
|
|
49
|
+
compress_request: options[:compress_request]
|
|
50
|
+
}
|
|
46
51
|
@transport = Transport.new(@transport_options)
|
|
47
52
|
end
|
|
48
53
|
|
data/lib/posthog/transport.rb
CHANGED
|
@@ -8,6 +8,7 @@ require 'posthog/backoff_policy'
|
|
|
8
8
|
require 'net/http'
|
|
9
9
|
require 'net/https'
|
|
10
10
|
require 'json'
|
|
11
|
+
require 'zlib'
|
|
11
12
|
|
|
12
13
|
module PostHog
|
|
13
14
|
# HTTP transport used by the SDK workers.
|
|
@@ -28,6 +29,7 @@ module PostHog
|
|
|
28
29
|
# @option options [Integer] :retries Number of retry attempts for retryable failures.
|
|
29
30
|
# @option options [PostHog::BackoffPolicy] :backoff_policy Backoff policy used between retries.
|
|
30
31
|
# @option options [Boolean] :skip_ssl_verification Disable SSL certificate verification.
|
|
32
|
+
# @option options [Boolean] :compress_request Whether to gzip batch request bodies. Defaults to +true+.
|
|
31
33
|
def initialize(options = {})
|
|
32
34
|
if options[:api_host]
|
|
33
35
|
uri = URI.parse(options[:api_host])
|
|
@@ -44,6 +46,7 @@ module PostHog
|
|
|
44
46
|
@path = options[:path] || PATH
|
|
45
47
|
@retries = options[:retries] || RETRIES
|
|
46
48
|
@backoff_policy = options[:backoff_policy] || PostHog::BackoffPolicy.new
|
|
49
|
+
@compress_request = options[:compress_request] != false
|
|
47
50
|
|
|
48
51
|
http = Net::HTTP.new(options[:host], options[:port])
|
|
49
52
|
http.use_ssl = options[:ssl]
|
|
@@ -144,22 +147,41 @@ module PostHog
|
|
|
144
147
|
def send_request(api_key, batch)
|
|
145
148
|
payload = JSON.generate(api_key: api_key, batch: batch)
|
|
146
149
|
|
|
147
|
-
|
|
150
|
+
request_path, request_headers, request_payload = build_request(@path, @headers, payload)
|
|
151
|
+
request = Net::HTTP::Post.new(request_path, request_headers)
|
|
148
152
|
|
|
149
153
|
if self.class.stub
|
|
150
|
-
logger.debug "stubbed request to #{
|
|
154
|
+
logger.debug "stubbed request to #{request_path}: " \
|
|
151
155
|
"api key = #{api_key}, batch = #{JSON.generate(batch)}"
|
|
152
156
|
|
|
153
157
|
[200, '{}']
|
|
154
158
|
else
|
|
155
159
|
@http_mutex.synchronize do
|
|
156
160
|
@http.start unless @http.started? # Maintain a persistent connection
|
|
157
|
-
response = @http.request(request,
|
|
161
|
+
response = @http.request(request, request_payload)
|
|
158
162
|
[response.code.to_i, response.body]
|
|
159
163
|
end
|
|
160
164
|
end
|
|
161
165
|
end
|
|
162
166
|
|
|
167
|
+
def build_request(path, headers, payload)
|
|
168
|
+
return [path, headers, payload] unless @compress_request
|
|
169
|
+
|
|
170
|
+
compressed_payload = gzip_payload(payload)
|
|
171
|
+
return [path, headers, payload] unless compressed_payload
|
|
172
|
+
|
|
173
|
+
compressed_headers = headers.merge('Content-Encoding' => 'gzip')
|
|
174
|
+
|
|
175
|
+
[path, compressed_headers, compressed_payload]
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def gzip_payload(payload)
|
|
179
|
+
Zlib.gzip(payload)
|
|
180
|
+
rescue Zlib::Error => e
|
|
181
|
+
logger.warn("gzip compression failed; sending uncompressed - #{e.message}")
|
|
182
|
+
nil
|
|
183
|
+
end
|
|
184
|
+
|
|
163
185
|
class << self
|
|
164
186
|
attr_writer :stub
|
|
165
187
|
|
data/lib/posthog/version.rb
CHANGED