posthog-ruby 3.9.1 → 3.10.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: 1388daa77d143c2822f15642b375b1e833a893d86677337d0d8bfc88875b364c
4
- data.tar.gz: 2e71adb4fdc958bd8793515dbb3317652d9e811166678e7e38c57e57a85f3a08
3
+ metadata.gz: caf866cb91c9fb07b894b6af005decf79644ddc3c2a7c2f84dc3dd59ac9350b1
4
+ data.tar.gz: 6aace1e7809b8f2c6f8529086aaaf8cceb44d5f47b515222d0eeb0e757a66617
5
5
  SHA512:
6
- metadata.gz: a3ab6ee23fabf6375facb994ed3a16de3ec21ec53c88da0121fe3158d9b6552127d081124bb02870ca0b04d47bd2df4a79b7a95c45d8991095e2a4cbdfc63d09
7
- data.tar.gz: ae04e209088f84ccb2760f0dc5fc3760cfb25e2ecd452201cf16b5432e4761acd63ef9471fc87cf88e21a1d16ce744191ea7ed6a445b2544fcf3e3a0f2f1040a
6
+ metadata.gz: ec98d033a36bd7069b001e957fd5a395d54225f505717c45322faa8ce157d00a8fdb1275b5ac4f098e3c51e78a8b2b13a11a94d76be7d50be9b87ab469da3285
7
+ data.tar.gz: c0122abe71ac22a9075236b167c4a136c156f8af6853252f0caf18e4c7f41f8ccdd4b2132481d1138a6ec44793efef8d14ed35c3b7aa5769f45b20babd1ebae9
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'time'
4
+ require 'json'
4
5
  require 'securerandom'
5
6
 
6
7
  require 'posthog/defaults'
@@ -51,7 +52,7 @@ module PostHog
51
52
  end
52
53
 
53
54
  # @param opts [Hash] Client configuration.
54
- # @option opts [String] :api_key Your project's API key. Required.
55
+ # @option opts [String, nil] :api_key Your project's API key. Missing or blank values disable the client.
55
56
  # @option opts [String, nil] :personal_api_key Your personal API key. Required for local feature flag evaluation.
56
57
  # @option opts [String] :host Fully qualified hostname of the PostHog server. Defaults to `https://us.i.posthog.com`.
57
58
  # @option opts [Integer] :max_queue_size Maximum number of calls to remain queued. Defaults to 10_000.
@@ -71,7 +72,10 @@ module PostHog
71
72
  # @option opts [Boolean] :skip_ssl_verification +true+ to disable SSL certificate verification for requests.
72
73
  # Intended only for local development or custom deployments.
73
74
  # @option opts [Object] :flag_definition_cache_provider An object implementing the {FlagDefinitionCacheProvider}
74
- # interface for distributed flag definition caching. EXPERIMENTAL: This API may change in future minor versions.
75
+ # interface for distributed flag definition caching.
76
+ # @option opts [Boolean] :is_server +true+ to stamp captured events with `$is_server => true` so PostHog
77
+ # attributes them as server-side. Defaults to +true+. Set to +false+ when running posthog-ruby as a
78
+ # client/CLI so the device OS is attributed normally and `$is_server` is omitted.
75
79
  def initialize(opts = {})
76
80
  symbolize_keys!(opts)
77
81
 
@@ -81,11 +85,12 @@ module PostHog
81
85
 
82
86
  @queue = Queue.new
83
87
  @api_key = opts[:api_key]
88
+ @disabled = @api_key.nil? || @api_key.empty?
84
89
  @max_queue_size = opts[:max_queue_size] || Defaults::Queue::MAX_SIZE
85
90
  @worker_mutex = Mutex.new
86
- @sync_mode = opts[:sync_mode] == true && !opts[:test_mode]
91
+ @sync_mode = opts[:sync_mode] == true && !opts[:test_mode] && !@disabled
87
92
  @on_error = opts[:on_error] || proc { |status, error| }
88
- @worker = if opts[:test_mode]
93
+ @worker = if opts[:test_mode] || @disabled
89
94
  NoopWorker.new(@queue)
90
95
  elsif @sync_mode
91
96
  nil
@@ -104,11 +109,12 @@ module PostHog
104
109
  @feature_flags_poller = nil
105
110
  @personal_api_key = opts[:personal_api_key]
106
111
 
107
- check_api_key!
108
- logger.error('api_key is empty after trimming whitespace; check your project API key') if @api_key == ''
112
+ if @disabled && !opts[:silence_disabled_client_error]
113
+ logger.error('api_key is missing or empty after trimming whitespace; check your project API key')
114
+ end
109
115
 
110
116
  # Warn when multiple clients are created with the same API key (can cause dropped events)
111
- unless opts[:test_mode] || opts[:disable_singleton_warning]
117
+ unless @disabled || opts[:test_mode] || opts[:disable_singleton_warning]
112
118
  previous_count = self.class._increment_instance_count(@api_key)
113
119
  if previous_count >= 1
114
120
  logger.warn(
@@ -120,22 +126,25 @@ module PostHog
120
126
  end
121
127
  end
122
128
 
123
- @feature_flags_poller =
124
- FeatureFlagsPoller.new(
125
- opts[:feature_flags_polling_interval],
126
- opts[:personal_api_key],
127
- @api_key,
128
- opts[:host],
129
- opts[:feature_flag_request_timeout_seconds] || Defaults::FeatureFlags::FLAG_REQUEST_TIMEOUT_SECONDS,
130
- opts[:on_error],
131
- flag_definition_cache_provider: opts[:flag_definition_cache_provider]
132
- )
129
+ unless @disabled
130
+ @feature_flags_poller =
131
+ FeatureFlagsPoller.new(
132
+ opts[:feature_flags_polling_interval],
133
+ opts[:personal_api_key],
134
+ @api_key,
135
+ opts[:host],
136
+ opts[:feature_flag_request_timeout_seconds] || Defaults::FeatureFlags::FLAG_REQUEST_TIMEOUT_SECONDS,
137
+ opts[:on_error],
138
+ flag_definition_cache_provider: opts[:flag_definition_cache_provider]
139
+ )
140
+ end
133
141
 
134
142
  @distinct_id_has_sent_flag_calls = SizeLimitedHash.new(Defaults::MAX_HASH_SIZE) do |hash, key|
135
143
  hash[key] = []
136
144
  end
137
145
 
138
146
  @before_send = opts[:before_send]
147
+ @is_server = opts.fetch(:is_server, true) != false
139
148
  @deprecation_emitted_for = Concurrent::Set.new
140
149
  end
141
150
 
@@ -197,6 +206,8 @@ module PostHog
197
206
  # @return [Boolean] Whether the event was queued or sent.
198
207
  # @macro common_attrs
199
208
  def capture(attrs)
209
+ return false if @disabled
210
+
200
211
  symbolize_keys! attrs
201
212
  enrich_capture_attrs_with_context(attrs)
202
213
 
@@ -225,7 +236,7 @@ module PostHog
225
236
  end
226
237
 
227
238
  send_feature_flags_param = attrs[:send_feature_flags]
228
- if send_feature_flags_param
239
+ if send_feature_flags_param && !@disabled
229
240
  _emit_deprecation(
230
241
  :capture_send_feature_flags,
231
242
  '`send_feature_flags` on `capture` is deprecated and will be removed in a future major ' \
@@ -265,6 +276,7 @@ module PostHog
265
276
  attrs[:feature_variants] = feature_variants if feature_variants
266
277
  end
267
278
 
279
+ attrs[:is_server] = @is_server
268
280
  enqueue(FieldParser.parse_for_capture(attrs))
269
281
  end
270
282
 
@@ -279,6 +291,8 @@ module PostHog
279
291
  # same `$feature/<key>` and `$active_feature_flags` properties as the snapshot.
280
292
  # @return [Boolean, nil] Whether the exception event was queued or sent, or nil if the input could not be parsed.
281
293
  def capture_exception(exception, distinct_id = nil, additional_properties = {}, flags: nil)
294
+ return false if @disabled
295
+
282
296
  exception_info = ExceptionCapture.build_parsed_exception(exception)
283
297
 
284
298
  return if exception_info.nil?
@@ -305,7 +319,10 @@ module PostHog
305
319
  # @return [Boolean] Whether the identify event was queued or sent.
306
320
  # @macro common_attrs
307
321
  def identify(attrs)
322
+ return false if @disabled
323
+
308
324
  symbolize_keys! attrs
325
+ attrs[:is_server] = @is_server
309
326
  enqueue(FieldParser.parse_for_identify(attrs))
310
327
  end
311
328
 
@@ -320,7 +337,10 @@ module PostHog
320
337
  # @return [Boolean] Whether the group identify event was queued or sent.
321
338
  # @macro common_attrs
322
339
  def group_identify(attrs)
340
+ return false if @disabled
341
+
323
342
  symbolize_keys! attrs
343
+ attrs[:is_server] = @is_server
324
344
  enqueue(FieldParser.parse_for_group_identify(attrs))
325
345
  end
326
346
 
@@ -332,7 +352,10 @@ module PostHog
332
352
  # @return [Boolean] Whether the alias event was queued or sent.
333
353
  # @macro common_attrs
334
354
  def alias(attrs)
355
+ return false if @disabled
356
+
335
357
  symbolize_keys! attrs
358
+ attrs[:is_server] = @is_server
336
359
  enqueue(FieldParser.parse_for_alias(attrs))
337
360
  end
338
361
 
@@ -387,6 +410,8 @@ module PostHog
387
410
  # @param flag_key [String, Symbol] The unique flag key of the remote config feature flag.
388
411
  # @return [Hash] The parsed remote config payload response.
389
412
  def get_remote_config_payload(flag_key)
413
+ return nil if @disabled
414
+
390
415
  @feature_flags_poller.get_remote_config_payload(flag_key.to_s)
391
416
  end
392
417
 
@@ -464,6 +489,8 @@ module PostHog
464
489
  '`flags.get_flag_payload(key)` instead — this consolidates flag evaluation into a single ' \
465
490
  '`/flags` request per incoming request.'
466
491
  )
492
+ return nil if @disabled
493
+
467
494
  _get_feature_flag_result(
468
495
  key, distinct_id,
469
496
  groups: groups, person_properties: person_properties, group_properties: group_properties,
@@ -506,6 +533,8 @@ module PostHog
506
533
  return FeatureFlagEvaluations.new(host: host, distinct_id: '', flags: {})
507
534
  end
508
535
 
536
+ return FeatureFlagEvaluations.new(host: host, distinct_id: distinct_id, flags: {}, groups: groups) if @disabled
537
+
509
538
  person_properties, group_properties = add_local_person_and_group_properties(
510
539
  distinct_id, groups, person_properties, group_properties
511
540
  )
@@ -619,6 +648,8 @@ module PostHog
619
648
  group_properties: {},
620
649
  only_evaluate_locally: false
621
650
  )
651
+ return {} if @disabled
652
+
622
653
  person_properties, group_properties = add_local_person_and_group_properties(distinct_id, groups,
623
654
  person_properties, group_properties)
624
655
  @feature_flags_poller.get_all_flags(distinct_id, groups, person_properties, group_properties,
@@ -656,6 +687,8 @@ module PostHog
656
687
  'instead — this consolidates flag evaluation into a single `/flags` request per ' \
657
688
  'incoming request.'
658
689
  )
690
+ return nil if @disabled
691
+
659
692
  key = key.to_s
660
693
  person_properties, group_properties = add_local_person_and_group_properties(distinct_id, groups,
661
694
  person_properties, group_properties)
@@ -682,6 +715,8 @@ module PostHog
682
715
  group_properties: {},
683
716
  only_evaluate_locally: false
684
717
  )
718
+ return { featureFlags: {}, featureFlagPayloads: {} } if @disabled
719
+
685
720
  person_properties, group_properties = add_local_person_and_group_properties(
686
721
  distinct_id, groups, person_properties, group_properties
687
722
  )
@@ -699,6 +734,8 @@ module PostHog
699
734
  #
700
735
  # @return [void]
701
736
  def reload_feature_flags
737
+ return if @disabled
738
+
702
739
  unless @personal_api_key
703
740
  logger.error(
704
741
  'You need to specify a personal_api_key to locally evaluate feature flags'
@@ -712,8 +749,8 @@ module PostHog
712
749
  #
713
750
  # @return [void]
714
751
  def shutdown
715
- self.class._decrement_instance_count(@api_key) if @api_key
716
- @feature_flags_poller.shutdown_poller
752
+ self.class._decrement_instance_count(@api_key) unless @disabled
753
+ @feature_flags_poller&.shutdown_poller
717
754
  flush
718
755
  if @sync_mode
719
756
  @sync_lock.synchronize { @transport&.shutdown }
@@ -760,11 +797,22 @@ module PostHog
760
797
  # Shared by the legacy single-flag path ({#get_feature_flag_result}) and the
761
798
  # snapshot's access-recording. Owns dedup-key construction, the
762
799
  # per-distinct_id sent-flags cache, and the `$feature_flag_called` capture call.
800
+ # Group context is included in the dedup key so group-scoped flags fire a
801
+ # separate event for each group a user is evaluated under.
763
802
  def _capture_feature_flag_called_if_needed(
764
803
  distinct_id: nil, key: nil, response: nil, properties: nil,
765
804
  groups: nil, disable_geoip: nil
766
805
  )
767
- reported_key = "#{key}_#{response.nil? ? '::null::' : response}"
806
+ response_repr = response.nil? ? '::null::' : response
807
+ groups_repr =
808
+ if groups && !groups.empty?
809
+ # Canonicalize so two equal hashes with keys inserted in a different
810
+ # order produce the same dedup key.
811
+ "_#{groups.sort.to_json}"
812
+ else
813
+ ''
814
+ end
815
+ reported_key = "#{key}_#{response_repr}#{groups_repr}"
768
816
  return if @distinct_id_has_sent_flag_calls[distinct_id].include?(reported_key)
769
817
 
770
818
  msg = {
@@ -798,6 +846,8 @@ module PostHog
798
846
  only_evaluate_locally: false,
799
847
  send_feature_flag_events: true
800
848
  )
849
+ return nil if @disabled
850
+
801
851
  key = key.to_s
802
852
  person_properties, group_properties = add_local_person_and_group_properties(
803
853
  distinct_id, groups, person_properties, group_properties
@@ -863,6 +913,8 @@ module PostHog
863
913
  #
864
914
  # returns Boolean of whether the item was added to the queue.
865
915
  def enqueue(action)
916
+ return false if @disabled
917
+
866
918
  action = process_before_send(action)
867
919
  return false if action.nil? || action.empty?
868
920
 
@@ -889,11 +941,6 @@ module PostHog
889
941
  end
890
942
  end
891
943
 
892
- # private: Checks that the api_key is properly initialized
893
- def check_api_key!
894
- raise ArgumentError, 'API key must be initialized' if @api_key.nil?
895
- end
896
-
897
944
  def normalize_string_option(value, blank_as_nil: false)
898
945
  return value unless value.is_a?(String)
899
946
 
@@ -89,11 +89,11 @@ module PostHog
89
89
  common.merge(
90
90
  {
91
91
  event: '$groupidentify',
92
- properties: {
92
+ properties: (common[:properties] || {}).merge(
93
93
  '$group_type': group_type,
94
94
  '$group_key': group_key,
95
- '$group_set': properties.merge(common[:properties] || {})
96
- }
95
+ '$group_set': properties
96
+ )
97
97
  }
98
98
  )
99
99
  end
@@ -141,16 +141,21 @@ module PostHog
141
141
  check_timestamp! timestamp
142
142
  check_presence! distinct_id, 'distinct_id'
143
143
 
144
+ is_server = fields.fetch(:is_server, true) != false
145
+
146
+ properties = {
147
+ '$lib' => 'posthog-ruby',
148
+ '$lib_version' => PostHog::VERSION.to_s
149
+ }
150
+ properties['$is_server'] = true if is_server
151
+
144
152
  parsed = {
145
153
  timestamp: datetime_in_iso8601(timestamp),
146
154
  library: 'posthog-ruby',
147
155
  library_version: PostHog::VERSION.to_s,
148
156
  messageId: message_id,
149
157
  distinct_id: distinct_id,
150
- properties: {
151
- '$lib' => 'posthog-ruby',
152
- '$lib_version' => PostHog::VERSION.to_s
153
- }
158
+ properties: properties
154
159
  }
155
160
 
156
161
  if send_feature_flags && fields[:feature_variants]
@@ -3,8 +3,6 @@
3
3
  module PostHog
4
4
  # Interface for external caching of feature flag definitions.
5
5
  #
6
- # EXPERIMENTAL: This API may change in future minor version bumps.
7
- #
8
6
  # Enables multi-worker environments (Kubernetes, load-balanced servers,
9
7
  # serverless functions) to share flag definitions via an external cache,
10
8
  # reducing redundant API calls.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PostHog
4
- VERSION = '3.9.1'
4
+ VERSION = '3.10.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.9.1
4
+ version: 3.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ''