posthog-ruby 3.20.0 → 3.22.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 36dffe9438cc269541f1642261b27b020bf05993eda1ad9999887428caa1667b
4
- data.tar.gz: 9575ff454af5b89c0be1eb6abf4c7a1158fdbca88d4c708860339a1710b1393d
3
+ metadata.gz: b47ba497bca9801a2d3a530351c021a9441368b63c16cebb2e04dcfe7d0d3b21
4
+ data.tar.gz: ab1cb2e8238e5f19e3d9c33d96386c98478e357d7bdb96121d8332910f61477a
5
5
  SHA512:
6
- metadata.gz: 9badf7f03c2009d4c643b6e41f4ede382c0bf488eb0ae4d5cf33ec454110102e3fc342b5f3357b58b881f2a513628ba6056f0ccfa82d54ac8cafb4d814ae3811
7
- data.tar.gz: e7918e45f9645da0052d65d205359e8384709f645f0f1b5201500a5cc97bb092aed771cd7fac7846f5bfc142fcb9e8168f55d9cbc750c6bbb8aab26e60e1138d
6
+ metadata.gz: cc57a456ab1f6ecdce0508ccea5a5896e42901938e8eb8d29b2f8ec07bd1a16c069030feed87aa196a604fffd2e3d0e40999f12434dca839e3aa507f008afdb7
7
+ data.tar.gz: d4c2693b6a8c60706707599d2958cdf24c54aa21f4ee8d067fbdae1cf9624fad08c923d95e67c5a629cd69aaee420e221a1d7edc51422d644bef8e4218d60b66
@@ -22,6 +22,31 @@ module PostHog
22
22
  include PostHog::Utils
23
23
  include PostHog::Logging
24
24
 
25
+ # Strict allowlist of properties kept on minimal `$feature_flag_called`
26
+ # events (server-gated, non-experiment flags only). Everything else —
27
+ # context properties, `$feature/<key>`, payloads, system metadata — is
28
+ # stripped. Includes symbol forms so allowlisted properties supplied
29
+ # through context with symbol keys survive.
30
+ MINIMAL_FLAG_CALLED_EVENT_PROPERTIES = %w[
31
+ $feature_flag
32
+ $feature_flag_response
33
+ $feature_flag_has_experiment
34
+ $feature_flag_id
35
+ $feature_flag_version
36
+ $feature_flag_reason
37
+ $feature_flag_request_id
38
+ $feature_flag_evaluated_at
39
+ $feature_flag_error
40
+ locally_evaluated
41
+ $groups
42
+ $process_person_profile
43
+ $session_id
44
+ $is_server
45
+ $lib
46
+ $lib_version
47
+ ].flat_map { |key| [key, key.to_sym] }.freeze
48
+ private_constant :MINIMAL_FLAG_CALLED_EVENT_PROPERTIES
49
+
25
50
  # Thread-safe tracking of client instances per API key for singleton warnings
26
51
  @instances_by_api_key = {}
27
52
  @instances_mutex = Mutex.new
@@ -118,7 +143,10 @@ module PostHog
118
143
  @max_queue_size = opts[:max_queue_size] || Defaults::Queue::MAX_SIZE
119
144
  @worker_mutex = Mutex.new
120
145
  @shutdown_mutex = Mutex.new
146
+ @shutdown_condition = ConditionVariable.new
121
147
  @shutdown = false
148
+ @shutdown_complete = false
149
+ @shutdown_result = false
122
150
  @sync_mode = opts[:sync_mode] == true && !opts[:test_mode] && !@disabled
123
151
  @on_error = opts[:on_error] || proc { |status, error| }
124
152
  @worker = if opts[:test_mode] || @disabled
@@ -184,29 +212,40 @@ module PostHog
184
212
  @deprecation_emitted_for = Concurrent::Set.new
185
213
  end
186
214
 
187
- # Synchronously waits until the worker has cleared the queue.
215
+ # When sync_mode is enabled, blocks until in-flight requests are complete
216
+ # and returns true. Timeout parameter has no effect.
188
217
  #
189
- # Use only for scripts which are not long-running, and will specifically
190
- # exit.
218
+ # Otherwise, waits until the worker has cleared the queue or timeout is hit.
219
+ # Note: The asynchronous wait polls and can be starved if another thread is
220
+ # actively continuing to enqueue new events.
191
221
  #
192
- # @return [void]
193
- def flush
222
+ # @param timeout [Numeric, nil] Maximum seconds to wait for pending events
223
+ # to be sent, or +nil+ to wait indefinitely.
224
+ # @return [Boolean] +true+ if all pending events were sent, +false+ if the
225
+ # timeout elapsed first.
226
+ def flush(timeout: nil)
194
227
  if @sync_mode
195
228
  # Wait for any in-flight sync send to complete
196
229
  @sync_lock.synchronize {} # rubocop:disable Lint/EmptyBlock
197
- return
230
+ return true
198
231
  end
199
232
 
200
233
  if @worker.is_a?(NoopWorker)
201
234
  clear
202
- return
235
+ return true
203
236
  end
204
237
 
238
+ deadline = timeout && (monotonic_time + timeout)
239
+
205
240
  while !@queue.empty? || @worker.is_requesting?
206
241
  ensure_worker_running
207
242
  @worker.request_flush
208
- sleep(0.1)
243
+ remaining = deadline && (deadline - monotonic_time)
244
+ return false if remaining && remaining <= 0
245
+
246
+ sleep(remaining ? remaining.clamp(0, 0.1) : 0.1)
209
247
  end
248
+ true
210
249
  end
211
250
 
212
251
  # Clears the queue without waiting.
@@ -253,6 +292,7 @@ module PostHog
253
292
  return false if @disabled
254
293
 
255
294
  symbolize_keys! attrs
295
+ minimal_flag_called_event = attrs.delete(:_minimal_flag_called_event) == true
256
296
  enrich_capture_attrs_with_context(attrs)
257
297
 
258
298
  # Precedence: an explicit `flags` snapshot always wins, regardless of
@@ -321,7 +361,13 @@ module PostHog
321
361
  end
322
362
 
323
363
  attrs[:is_server] = @is_server
324
- enqueue(FieldParser.parse_for_capture(attrs))
364
+ message = FieldParser.parse_for_capture(attrs)
365
+ # Minimal events are built from the allowlist after full assembly so
366
+ # context properties and parser-added metadata can never leak in.
367
+ if minimal_flag_called_event
368
+ message[:properties] = message[:properties].slice(*MINIMAL_FLAG_CALLED_EVENT_PROPERTIES)
369
+ end
370
+ enqueue(message)
325
371
  end
326
372
 
327
373
  # Captures an exception as an event
@@ -627,6 +673,11 @@ module PostHog
627
673
  evaluated_at = nil
628
674
  errors_while_computing = false
629
675
  quota_limited = false
676
+ # Server-controlled gate for minimal `$feature_flag_called` events. When
677
+ # the snapshot uses a remote /flags response, the response's top-level
678
+ # `minimalFlagCalledEvents` field governs; a local-only snapshot reads
679
+ # the gate polled with the flag definitions.
680
+ minimal_flag_called_events = @feature_flags_poller.minimal_flag_called_events
630
681
 
631
682
  # Skip the remote `/flags` round-trip when the caller scoped the request
632
683
  # to a fixed set of `flag_keys` and we've already resolved every one of
@@ -635,10 +686,16 @@ module PostHog
635
686
  all_requested_flags_resolved_locally = flag_keys_set && (flag_keys_set - locally_evaluated_keys).empty?
636
687
 
637
688
  if !only_evaluate_locally && !all_requested_flags_resolved_locally
689
+ # The gate is team-level, so the /flags response gate supersedes the
690
+ # poller gate for mixed snapshots — both signals come from the same
691
+ # server and agree in steady state. When the response omits the gate,
692
+ # the whole snapshot fails safe to full events.
693
+ minimal_flag_called_events = false
638
694
  begin
639
695
  flags_response = @feature_flags_poller.get_flags(
640
696
  distinct_id, groups, person_properties, group_properties, flag_keys, disable_geoip
641
697
  )
698
+ minimal_flag_called_events = flags_response[:minimalFlagCalledEvents] == true
642
699
  request_id = flags_response[:requestId]
643
700
  evaluated_at = flags_response[:evaluatedAt]
644
701
  errors_while_computing = flags_response[:errorsWhileComputingFlags] == true
@@ -677,7 +734,8 @@ module PostHog
677
734
  evaluated_at: evaluated_at,
678
735
  flag_definitions_loaded_at: @feature_flags_poller.flag_definitions_loaded_at,
679
736
  errors_while_computing: errors_while_computing,
680
- quota_limited: quota_limited
737
+ quota_limited: quota_limited,
738
+ minimal_flag_called_events: minimal_flag_called_events
681
739
  )
682
740
  end
683
741
 
@@ -777,6 +835,7 @@ module PostHog
777
835
  response.delete(:requestId)
778
836
  response.delete(:evaluatedAt)
779
837
  response.delete(:flagDetails)
838
+ response.delete(:minimalFlagCalledEvents)
780
839
  response
781
840
  end
782
841
 
@@ -817,29 +876,65 @@ module PostHog
817
876
 
818
877
  # Flush pending events and stop background resources.
819
878
  #
820
- # @return [void]
821
- def shutdown
822
- already_shutdown = @shutdown_mutex.synchronize do
879
+ # When sync_mode is not set, this method calls the flush method; see flush
880
+ # method documentation for timeout semantics. Unlike flush, this method
881
+ # stops the worker. So any events still queued after the timeout will not be
882
+ # sent. After the timeout, this method may wait up to one additional second
883
+ # for worker and transport cleanup, which may continue after it returns.
884
+ #
885
+ # @param timeout [Numeric, nil] Maximum seconds to wait for pending events
886
+ # to be sent, or +nil+ to wait indefinitely. Has no effect when sync_mode
887
+ # is true.
888
+ # @return [Boolean] +true+ if all pending events were sent, +false+ if the
889
+ # timeout elapsed first.
890
+ def shutdown(timeout: nil)
891
+ deadline = timeout && (monotonic_time + timeout)
892
+ shutdown_result = @shutdown_mutex.synchronize do
823
893
  if @shutdown
824
- true
894
+ until @shutdown_complete
895
+ # Avoid deadlocking when an in-flight send's on_error callback
896
+ # re-enters shutdown while the original caller is waiting for it.
897
+ break if Thread.current == @worker_thread || (@sync_mode && @sync_lock.owned?)
898
+
899
+ remaining = deadline && (deadline - monotonic_time)
900
+ break if remaining && remaining <= 0
901
+
902
+ @shutdown_condition.wait(@shutdown_mutex, remaining)
903
+ end
904
+ @shutdown_complete ? @shutdown_result : false
825
905
  else
826
906
  @shutdown = true
827
- false
907
+ nil
828
908
  end
829
909
  end
830
- return if already_shutdown
910
+ return shutdown_result unless shutdown_result.nil?
831
911
 
832
- self.class._decrement_instance_count(@api_key) unless @disabled
833
- @feature_flags_poller&.shutdown_poller
834
- flush
835
- if @sync_mode
836
- @sync_lock.synchronize { @transport&.shutdown }
837
- else
838
- @worker&.shutdown
839
- @worker_thread&.join(1)
840
- end
841
- @distinct_id_has_sent_flag_calls_mutex.synchronize do
842
- @distinct_id_has_sent_flag_calls.clear
912
+ flushed = false
913
+ begin
914
+ self.class._decrement_instance_count(@api_key) unless @disabled
915
+ @feature_flags_poller&.shutdown_poller
916
+ flushed =
917
+ if @sync_mode
918
+ # Waiting for @sync_lock lets any in-flight sync send finish before
919
+ # the connection is closed.
920
+ @sync_lock.synchronize { @transport&.shutdown }
921
+ true
922
+ else
923
+ drained = flush(timeout: timeout)
924
+ @worker&.shutdown
925
+ @worker_thread&.join(1)
926
+ drained
927
+ end
928
+ @distinct_id_has_sent_flag_calls_mutex.synchronize do
929
+ @distinct_id_has_sent_flag_calls.clear
930
+ end
931
+ flushed
932
+ ensure
933
+ @shutdown_mutex.synchronize do
934
+ @shutdown_result = flushed
935
+ @shutdown_complete = true
936
+ @shutdown_condition.broadcast
937
+ end
843
938
  end
844
939
  end
845
940
 
@@ -885,7 +980,7 @@ module PostHog
885
980
  # separate event for each group a user is evaluated under.
886
981
  def _capture_feature_flag_called_if_needed(
887
982
  distinct_id: nil, key: nil, response: nil, properties: nil,
888
- groups: nil, disable_geoip: nil
983
+ groups: nil, disable_geoip: nil, minimal: false
889
984
  )
890
985
  response_repr = response.nil? ? '::null::' : response
891
986
  groups_repr =
@@ -915,6 +1010,7 @@ module PostHog
915
1010
  }
916
1011
  msg[:groups] = groups if groups
917
1012
  msg[:disable_geoip] = disable_geoip unless disable_geoip.nil?
1013
+ msg[:_minimal_flag_called_event] = true if minimal
918
1014
 
919
1015
  capture(msg)
920
1016
  end
@@ -945,7 +1041,7 @@ module PostHog
945
1041
  groups, person_properties, group_properties
946
1042
  )
947
1043
  feature_flag_response, flag_was_locally_evaluated, request_id, evaluated_at, feature_flag_error, payload,
948
- has_experiment =
1044
+ has_experiment, minimal_flag_called_events =
949
1045
  @feature_flags_poller.get_feature_flag(
950
1046
  key, distinct_id, groups, person_properties, group_properties, only_evaluate_locally
951
1047
  )
@@ -960,9 +1056,13 @@ module PostHog
960
1056
  properties['$feature_flag_evaluated_at'] = evaluated_at if evaluated_at
961
1057
  properties['$feature_flag_error'] = feature_flag_error if feature_flag_error
962
1058
 
1059
+ # Emit a minimal event only when the server gate is on and the flag is
1060
+ # known to have no linked experiment. Any missing signal fails safe to
1061
+ # the full event.
1062
+ minimal = minimal_flag_called_events == true && has_experiment == false
963
1063
  _capture_feature_flag_called_if_needed(
964
1064
  distinct_id: distinct_id, key: key, response: feature_flag_response,
965
- properties: properties, groups: groups
1065
+ properties: properties, groups: groups, minimal: minimal
966
1066
  )
967
1067
  end
968
1068
 
@@ -44,6 +44,8 @@ module PostHog
44
44
  # @param flag_definitions_loaded_at [Time, nil] When local flag definitions were loaded.
45
45
  # @param errors_while_computing [Boolean] Whether the server reported errors while computing flags.
46
46
  # @param quota_limited [Boolean] Whether feature flag evaluation was quota limited.
47
+ # @param minimal_flag_called_events [Boolean] Server-controlled gate for minimal
48
+ # `$feature_flag_called` events.
47
49
  # @param accessed [Array<String>, Set<String>, nil] Flag keys already accessed by this snapshot.
48
50
  def initialize(
49
51
  host: nil,
@@ -56,6 +58,7 @@ module PostHog
56
58
  flag_definitions_loaded_at: nil,
57
59
  errors_while_computing: false,
58
60
  quota_limited: false,
61
+ minimal_flag_called_events: false,
59
62
  accessed: nil
60
63
  )
61
64
  @host = host
@@ -68,6 +71,7 @@ module PostHog
68
71
  @flag_definitions_loaded_at = flag_definitions_loaded_at
69
72
  @errors_while_computing = errors_while_computing
70
73
  @quota_limited = quota_limited
74
+ @minimal_flag_called_events = minimal_flag_called_events == true
71
75
  @accessed = Set.new(accessed || [])
72
76
  end
73
77
 
@@ -188,13 +192,19 @@ module PostHog
188
192
  errors << 'flag_missing' if flag.nil?
189
193
  properties['$feature_flag_error'] = errors.join(',') unless errors.empty?
190
194
 
195
+ # Emit a minimal event only when the server gate is on and the flag is
196
+ # known to have no linked experiment. Any missing signal (unknown flag,
197
+ # has_experiment absent) fails safe to the full event.
198
+ minimal = @minimal_flag_called_events && !flag.nil? && flag.has_experiment == false
199
+
191
200
  @host.capture_flag_called_event_if_needed.call(
192
201
  distinct_id: @distinct_id,
193
202
  key: key,
194
203
  response: response,
195
204
  properties: properties,
196
205
  groups: @groups,
197
- disable_geoip: @disable_geoip
206
+ disable_geoip: @disable_geoip,
207
+ minimal: minimal
198
208
  )
199
209
  end
200
210
 
@@ -210,6 +220,7 @@ module PostHog
210
220
  flag_definitions_loaded_at: @flag_definitions_loaded_at,
211
221
  errors_while_computing: @errors_while_computing,
212
222
  quota_limited: @quota_limited,
223
+ minimal_flag_called_events: @minimal_flag_called_events,
213
224
  accessed: @accessed.dup
214
225
  )
215
226
  end
@@ -72,7 +72,10 @@ module PostHog
72
72
  @flags_etag = Concurrent::AtomicReference.new(nil)
73
73
  @flag_definitions_loaded_at = Concurrent::AtomicReference.new(nil)
74
74
  @async_load = async_load
75
-
75
+ # Server-controlled gate for minimal `$feature_flag_called` events, read
76
+ # from the top-level `minimal_flag_called_events` key of the local
77
+ # evaluation definitions payload. false when the server does not send it.
78
+ @minimal_flag_called_events = false
76
79
  @flag_definition_cache_provider = flag_definition_cache_provider
77
80
  FlagDefinitionCacheProvider.validate!(@flag_definition_cache_provider) if @flag_definition_cache_provider
78
81
 
@@ -111,7 +114,7 @@ module PostHog
111
114
  @flag_definitions_loaded_at.value
112
115
  end
113
116
 
114
- attr_reader :feature_flags_by_key
117
+ attr_reader :feature_flags_by_key, :minimal_flag_called_events
115
118
 
116
119
  def get_feature_variants(
117
120
  distinct_id,
@@ -245,6 +248,11 @@ module PostHog
245
248
  # evaluated flags carry it in the response metadata. nil when the server
246
249
  # (an older deployment) does not report it.
247
250
  has_experiment = feature_flag[:has_experiment] if flag_was_locally_evaluated
251
+ # Server-controlled gate for minimal `$feature_flag_called` events.
252
+ # Locally-evaluated flags read it from the definitions payload; remotely
253
+ # evaluated flags read it from the /flags response. nil when the signal
254
+ # is unavailable, which fails safe to the full event.
255
+ minimal_flag_called_events = @minimal_flag_called_events if flag_was_locally_evaluated
248
256
 
249
257
  request_id = nil
250
258
  evaluated_at = nil
@@ -279,6 +287,7 @@ module PostHog
279
287
 
280
288
  flag_detail = flags_data[:flagDetails]&.[](key.to_sym)
281
289
  has_experiment = flag_detail&.metadata&.has_experiment
290
+ minimal_flag_called_events = flags_data[:minimalFlagCalledEvents]
282
291
 
283
292
  logger.debug "Successfully computed flag remotely: #{key} -> #{response}"
284
293
  rescue Timeout::Error => e
@@ -293,7 +302,8 @@ module PostHog
293
302
  end
294
303
  end
295
304
 
296
- [response, flag_was_locally_evaluated, request_id, evaluated_at, feature_flag_error, payload, has_experiment]
305
+ [response, flag_was_locally_evaluated, request_id, evaluated_at, feature_flag_error, payload, has_experiment,
306
+ minimal_flag_called_events]
297
307
  end
298
308
 
299
309
  def get_all_flags(
@@ -352,6 +362,7 @@ module PostHog
352
362
  quota_limited = nil
353
363
  status_code = nil
354
364
  flag_details = nil
365
+ minimal_flag_called_events = nil
355
366
 
356
367
  if fallback_to_server && !only_evaluate_locally
357
368
  begin
@@ -367,6 +378,7 @@ module PostHog
367
378
 
368
379
  request_id = flags_and_payloads[:requestId]
369
380
  evaluated_at = flags_and_payloads[:evaluatedAt]
381
+ minimal_flag_called_events = flags_and_payloads[:minimalFlagCalledEvents]
370
382
 
371
383
  # Check if feature_flags are quota limited
372
384
  if quota_limited&.include?('feature_flags')
@@ -402,7 +414,8 @@ module PostHog
402
414
  evaluatedAt: evaluated_at,
403
415
  errorsWhileComputingFlags: errors_while_computing,
404
416
  quotaLimited: quota_limited,
405
- status: status_code
417
+ status: status_code,
418
+ minimalFlagCalledEvents: minimal_flag_called_events
406
419
  }
407
420
  end
408
421
 
@@ -1203,6 +1216,7 @@ module PostHog
1203
1216
  @group_type_mapping = Concurrent::Hash.new
1204
1217
  @cohorts = Concurrent::Hash.new
1205
1218
  @flag_definitions_loaded_at.value = nil
1219
+ @minimal_flag_called_events = false
1206
1220
  @loaded_flags_successfully_once.make_false
1207
1221
  @quota_limited.make_true
1208
1222
  return
@@ -1226,7 +1240,8 @@ module PostHog
1226
1240
  data = {
1227
1241
  flags: @feature_flags.to_a,
1228
1242
  group_type_mapping: @group_type_mapping.to_h,
1229
- cohorts: @cohorts.to_h
1243
+ cohorts: @cohorts.to_h,
1244
+ minimal_flag_called_events: @minimal_flag_called_events
1230
1245
  }
1231
1246
  @flag_definition_cache_provider.on_flag_definitions_received(data)
1232
1247
  rescue StandardError => e
@@ -1238,6 +1253,7 @@ module PostHog
1238
1253
  flags = get_by_symbol_or_string_key(data, 'flags') || []
1239
1254
  group_type_mapping = get_by_symbol_or_string_key(data, 'group_type_mapping') || {}
1240
1255
  cohorts = get_by_symbol_or_string_key(data, 'cohorts') || {}
1256
+ minimal_flag_called_events = get_by_symbol_or_string_key(data, 'minimal_flag_called_events')
1241
1257
 
1242
1258
  @feature_flags = Concurrent::Array.new(flags.map { |f| deep_symbolize_keys(f) })
1243
1259
 
@@ -1249,6 +1265,7 @@ module PostHog
1249
1265
 
1250
1266
  @group_type_mapping = Concurrent::Hash[deep_symbolize_keys(group_type_mapping)]
1251
1267
  @cohorts = Concurrent::Hash[deep_symbolize_keys(cohorts)]
1268
+ @minimal_flag_called_events = minimal_flag_called_events == true
1252
1269
 
1253
1270
  logger.debug "Loaded #{@feature_flags.length} feature flags and #{@cohorts.length} cohorts"
1254
1271
  @flag_definitions_loaded_at.value = (Time.now.to_f * 1000).to_i
@@ -14,9 +14,11 @@ module PostHog
14
14
  #
15
15
  # @!method flag_definitions
16
16
  # Retrieve cached flag definitions. Return a Hash with +:flags+,
17
- # +:group_type_mapping+, and +:cohorts+ keys, or +nil+ if the cache
18
- # is empty. Returning +nil+ triggers an API fetch when no flags are
19
- # loaded yet (emergency fallback).
17
+ # +:group_type_mapping+, +:cohorts+, and +:minimal_flag_called_events+
18
+ # keys, or +nil+ if the cache is empty. Returning +nil+ triggers an API
19
+ # fetch when no flags are loaded yet (emergency fallback). Providers
20
+ # written before +:minimal_flag_called_events+ existed continue to work;
21
+ # a missing key is treated as +false+.
20
22
  # @return [Hash, nil]
21
23
  #
22
24
  # @!method should_fetch_flag_definitions?
@@ -27,9 +29,9 @@ module PostHog
27
29
  #
28
30
  # @!method on_flag_definitions_received(data)
29
31
  # Called after successfully fetching new definitions from the API.
30
- # +data+ is a Hash with +:flags+, +:group_type_mapping+, and +:cohorts+
31
- # keys (plain Ruby types, not Concurrent:: wrappers). Store it in your
32
- # external cache.
32
+ # +data+ is a Hash with +:flags+, +:group_type_mapping+, +:cohorts+, and
33
+ # +:minimal_flag_called_events+ keys (plain Ruby types, not Concurrent::
34
+ # wrappers). Store it in your external cache.
33
35
  # @param data [Hash]
34
36
  # @return [void]
35
37
  #
@@ -213,9 +213,5 @@ module PostHog
213
213
  rescue StandardError => e
214
214
  logger.error("Error shutting down transport: #{e.message}")
215
215
  end
216
-
217
- def monotonic_time
218
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
219
- end
220
216
  end
221
217
  end
data/lib/posthog/utils.rb CHANGED
@@ -136,6 +136,12 @@ module PostHog
136
136
  end
137
137
  end
138
138
 
139
+ # public: Current monotonic clock time in seconds, for measuring durations
140
+ #
141
+ def monotonic_time
142
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
143
+ end
144
+
139
145
  # Hash that clears itself when it reaches a maximum length.
140
146
  #
141
147
  # @api private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PostHog
4
- VERSION = '3.20.0'
4
+ VERSION = '3.22.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: posthog-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.20.0
4
+ version: 3.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ''