posthog-ruby 3.8.1 → 3.9.1
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/backoff_policy.rb +4 -1
- data/lib/posthog/client.rb +121 -49
- data/lib/posthog/exception_capture.rb +22 -0
- data/lib/posthog/feature_flag.rb +18 -3
- data/lib/posthog/feature_flag_error.rb +1 -0
- data/lib/posthog/feature_flag_evaluations.rb +41 -2
- data/lib/posthog/feature_flag_result.rb +28 -5
- data/lib/posthog/feature_flags.rb +32 -15
- data/lib/posthog/field_parser.rb +11 -0
- data/lib/posthog/flag_definition_cache.rb +10 -4
- data/lib/posthog/internal/context.rb +119 -0
- data/lib/posthog/logging.rb +8 -1
- data/lib/posthog/message_batch.rb +8 -1
- data/lib/posthog/noop_worker.rb +7 -1
- data/lib/posthog/response.rb +5 -3
- data/lib/posthog/send_feature_flags_options.rb +17 -2
- data/lib/posthog/send_worker.rb +14 -7
- data/lib/posthog/transport.rb +18 -1
- data/lib/posthog/utils.rb +10 -0
- data/lib/posthog/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1388daa77d143c2822f15642b375b1e833a893d86677337d0d8bfc88875b364c
|
|
4
|
+
data.tar.gz: 2e71adb4fdc958bd8793515dbb3317652d9e811166678e7e38c57e57a85f3a08
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a3ab6ee23fabf6375facb994ed3a16de3ec21ec53c88da0121fe3158d9b6552127d081124bb02870ca0b04d47bd2df4a79b7a95c45d8991095e2a4cbdfc63d09
|
|
7
|
+
data.tar.gz: ae04e209088f84ccb2760f0dc5fc3760cfb25e2ecd452201cf16b5432e4761acd63ef9471fc87cf88e21a1d16ce744191ea7ed6a445b2544fcf3e3a0f2f1040a
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
require 'posthog/defaults'
|
|
4
4
|
|
|
5
5
|
module PostHog
|
|
6
|
+
# Retry backoff policy used by the SDK transport.
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
6
9
|
class BackoffPolicy
|
|
7
10
|
include PostHog::Defaults::BackoffPolicy
|
|
8
11
|
|
|
9
|
-
# @param [Hash]
|
|
12
|
+
# @param opts [Hash]
|
|
10
13
|
# @option opts [Numeric] :min_timeout_ms The minimum backoff timeout
|
|
11
14
|
# @option opts [Numeric] :max_timeout_ms The maximum backoff timeout
|
|
12
15
|
# @option opts [Numeric] :multiplier The value to multiply the current
|
data/lib/posthog/client.rb
CHANGED
|
@@ -14,6 +14,7 @@ require 'posthog/feature_flags'
|
|
|
14
14
|
require 'posthog/feature_flag_evaluations'
|
|
15
15
|
require 'posthog/send_feature_flags_options'
|
|
16
16
|
require 'posthog/exception_capture'
|
|
17
|
+
require 'posthog/internal/context'
|
|
17
18
|
|
|
18
19
|
module PostHog
|
|
19
20
|
class Client
|
|
@@ -49,29 +50,28 @@ module PostHog
|
|
|
49
50
|
end
|
|
50
51
|
end
|
|
51
52
|
|
|
52
|
-
# @param [Hash]
|
|
53
|
-
# @option opts [String] :api_key Your project's
|
|
54
|
-
# @option opts [String] :personal_api_key Your personal API key
|
|
55
|
-
# @option opts [
|
|
56
|
-
#
|
|
57
|
-
# @option opts [
|
|
58
|
-
#
|
|
59
|
-
# @option opts [
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
# @option opts [
|
|
63
|
-
#
|
|
64
|
-
# @option opts [Integer] :
|
|
65
|
-
#
|
|
66
|
-
# @option opts [
|
|
67
|
-
#
|
|
68
|
-
# @option opts [
|
|
69
|
-
#
|
|
70
|
-
# @option opts [
|
|
71
|
-
#
|
|
72
|
-
# @option opts [Object] :flag_definition_cache_provider An object implementing the
|
|
73
|
-
#
|
|
74
|
-
# EXPERIMENTAL: This API may change in future minor version bumps.
|
|
53
|
+
# @param opts [Hash] Client configuration.
|
|
54
|
+
# @option opts [String] :api_key Your project's API key. Required.
|
|
55
|
+
# @option opts [String, nil] :personal_api_key Your personal API key. Required for local feature flag evaluation.
|
|
56
|
+
# @option opts [String] :host Fully qualified hostname of the PostHog server. Defaults to `https://us.i.posthog.com`.
|
|
57
|
+
# @option opts [Integer] :max_queue_size Maximum number of calls to remain queued. Defaults to 10_000.
|
|
58
|
+
# @option opts [Integer] :batch_size Maximum number of events to send in one async batch.
|
|
59
|
+
# @option opts [Boolean] :test_mode +true+ if messages should remain queued for testing. Defaults to +false+.
|
|
60
|
+
# @option opts [Boolean] :sync_mode +true+ to send events synchronously on the calling thread. Useful in
|
|
61
|
+
# forking environments like Sidekiq and Resque. Defaults to +false+.
|
|
62
|
+
# @option opts [Proc] :on_error Callback invoked as `on_error.call(status, error)` for API or serialization errors.
|
|
63
|
+
# @option opts [Integer] :feature_flags_polling_interval How often to poll for feature flag definition changes,
|
|
64
|
+
# in seconds. Defaults to 30.
|
|
65
|
+
# @option opts [Integer] :feature_flag_request_timeout_seconds How long to wait for feature flag evaluation,
|
|
66
|
+
# in seconds. Defaults to 3.
|
|
67
|
+
# @option opts [Proc] :before_send A callback that receives the event hash and should return either a modified
|
|
68
|
+
# hash to be sent to PostHog or nil to prevent the event from being sent. e.g. `before_send: ->(event) { event }`.
|
|
69
|
+
# @option opts [Boolean] :disable_singleton_warning +true+ to suppress the warning when multiple clients share
|
|
70
|
+
# the same API key. Use only when you intentionally need multiple clients. Defaults to +false+.
|
|
71
|
+
# @option opts [Boolean] :skip_ssl_verification +true+ to disable SSL certificate verification for requests.
|
|
72
|
+
# Intended only for local development or custom deployments.
|
|
73
|
+
# @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
75
|
def initialize(opts = {})
|
|
76
76
|
symbolize_keys!(opts)
|
|
77
77
|
|
|
@@ -142,7 +142,9 @@ module PostHog
|
|
|
142
142
|
# Synchronously waits until the worker has cleared the queue.
|
|
143
143
|
#
|
|
144
144
|
# Use only for scripts which are not long-running, and will specifically
|
|
145
|
-
# exit
|
|
145
|
+
# exit.
|
|
146
|
+
#
|
|
147
|
+
# @return [void]
|
|
146
148
|
def flush
|
|
147
149
|
if @sync_mode
|
|
148
150
|
# Wait for any in-flight sync send to complete
|
|
@@ -158,7 +160,9 @@ module PostHog
|
|
|
158
160
|
|
|
159
161
|
# Clears the queue without waiting.
|
|
160
162
|
#
|
|
161
|
-
# Use only in test mode
|
|
163
|
+
# Use only in test mode.
|
|
164
|
+
#
|
|
165
|
+
# @return [void]
|
|
162
166
|
def clear
|
|
163
167
|
@queue.clear
|
|
164
168
|
end
|
|
@@ -175,8 +179,10 @@ module PostHog
|
|
|
175
179
|
#
|
|
176
180
|
# @option attrs [String] :event Event name
|
|
177
181
|
# @option attrs [Hash] :properties Event properties (optional)
|
|
178
|
-
# @option attrs [
|
|
179
|
-
#
|
|
182
|
+
# @option attrs [Hash] :groups Group analytics mapping from group type to group key (optional)
|
|
183
|
+
# @option attrs [Boolean, Hash, SendFeatureFlagsOptions] :send_feature_flags
|
|
184
|
+
# Deprecated. Whether to send feature flags with this event, or configuration for feature flag evaluation
|
|
185
|
+
# (optional)
|
|
180
186
|
# @option attrs [PostHog::FeatureFlagEvaluations] :flags A snapshot returned by
|
|
181
187
|
# {#evaluate_flags}. When present, `$feature/<key>` and `$active_feature_flags` are
|
|
182
188
|
# attached from the snapshot without making an additional /flags request, and this
|
|
@@ -185,9 +191,14 @@ module PostHog
|
|
|
185
191
|
# events in PostHog are deduplicated by the
|
|
186
192
|
# combination of teamId, timestamp date,
|
|
187
193
|
# event name, distinct id, and UUID
|
|
194
|
+
# @note If `:distinct_id` is omitted, request/context distinct_id is used when
|
|
195
|
+
# available; otherwise a UUID is generated and the event is marked personless
|
|
196
|
+
# with `$process_person_profile: false`.
|
|
197
|
+
# @return [Boolean] Whether the event was queued or sent.
|
|
188
198
|
# @macro common_attrs
|
|
189
199
|
def capture(attrs)
|
|
190
200
|
symbolize_keys! attrs
|
|
201
|
+
enrich_capture_attrs_with_context(attrs)
|
|
191
202
|
|
|
192
203
|
# Precedence: an explicit `flags` snapshot always wins, regardless of
|
|
193
204
|
# `send_feature_flags`. The snapshot guarantees the event carries the same
|
|
@@ -260,22 +271,20 @@ module PostHog
|
|
|
260
271
|
# Captures an exception as an event
|
|
261
272
|
#
|
|
262
273
|
# @param [Exception, String, Object] exception The exception to capture, a string message, or exception-like object
|
|
263
|
-
# @param [String] distinct_id The ID for the user (optional, defaults to
|
|
274
|
+
# @param [String] distinct_id The ID for the user (optional, defaults to request/context distinct_id
|
|
275
|
+
# or a generated UUID)
|
|
264
276
|
# @param [Hash] additional_properties Additional properties to include with the exception event (optional)
|
|
265
|
-
# @param [PostHog::FeatureFlagEvaluations]
|
|
277
|
+
# @param flags [PostHog::FeatureFlagEvaluations, nil] A snapshot returned by {#evaluate_flags}.
|
|
266
278
|
# Forwarded to the inner {#capture} call so the captured `$exception` event carries the
|
|
267
279
|
# same `$feature/<key>` and `$active_feature_flags` properties as the snapshot.
|
|
280
|
+
# @return [Boolean, nil] Whether the exception event was queued or sent, or nil if the input could not be parsed.
|
|
268
281
|
def capture_exception(exception, distinct_id = nil, additional_properties = {}, flags: nil)
|
|
269
282
|
exception_info = ExceptionCapture.build_parsed_exception(exception)
|
|
270
283
|
|
|
271
284
|
return if exception_info.nil?
|
|
272
285
|
|
|
273
|
-
no_distinct_id_was_provided = distinct_id.nil?
|
|
274
|
-
distinct_id ||= SecureRandom.uuid
|
|
275
|
-
|
|
276
286
|
properties = { '$exception_list' => [exception_info] }
|
|
277
287
|
properties.merge!(additional_properties) if additional_properties && !additional_properties.empty?
|
|
278
|
-
properties['$process_person_profile'] = false if no_distinct_id_was_provided
|
|
279
288
|
|
|
280
289
|
event_data = {
|
|
281
290
|
distinct_id: distinct_id,
|
|
@@ -293,6 +302,7 @@ module PostHog
|
|
|
293
302
|
# @param [Hash] attrs
|
|
294
303
|
#
|
|
295
304
|
# @option attrs [Hash] :properties User properties (optional)
|
|
305
|
+
# @return [Boolean] Whether the identify event was queued or sent.
|
|
296
306
|
# @macro common_attrs
|
|
297
307
|
def identify(attrs)
|
|
298
308
|
symbolize_keys! attrs
|
|
@@ -307,6 +317,7 @@ module PostHog
|
|
|
307
317
|
# @option attrs [String] :group_key Group key
|
|
308
318
|
# @option attrs [Hash] :properties Group properties (optional)
|
|
309
319
|
# @option attrs [String] :distinct_id Distinct ID (optional)
|
|
320
|
+
# @return [Boolean] Whether the group identify event was queued or sent.
|
|
310
321
|
# @macro common_attrs
|
|
311
322
|
def group_identify(attrs)
|
|
312
323
|
symbolize_keys! attrs
|
|
@@ -318,23 +329,32 @@ module PostHog
|
|
|
318
329
|
# @param [Hash] attrs
|
|
319
330
|
#
|
|
320
331
|
# @option attrs [String] :alias The alias to give the distinct id
|
|
332
|
+
# @return [Boolean] Whether the alias event was queued or sent.
|
|
321
333
|
# @macro common_attrs
|
|
322
334
|
def alias(attrs)
|
|
323
335
|
symbolize_keys! attrs
|
|
324
336
|
enqueue(FieldParser.parse_for_alias(attrs))
|
|
325
337
|
end
|
|
326
338
|
|
|
327
|
-
# @return [Hash]
|
|
339
|
+
# @return [Hash] Pops the last message from the queue. Intended for test mode.
|
|
328
340
|
def dequeue_last_message
|
|
329
341
|
@queue.pop
|
|
330
342
|
end
|
|
331
343
|
|
|
332
|
-
# @return [
|
|
344
|
+
# @return [Integer] Number of messages in the queue. Intended for test mode.
|
|
333
345
|
def queued_messages
|
|
334
346
|
@queue.length
|
|
335
347
|
end
|
|
336
348
|
|
|
337
|
-
# @deprecated Use {#evaluate_flags} and {FeatureFlagEvaluations#
|
|
349
|
+
# @deprecated Use {#evaluate_flags} and {FeatureFlagEvaluations#enabled?} instead.
|
|
350
|
+
# @param flag_key [String, Symbol] The unique key of the feature flag.
|
|
351
|
+
# @param distinct_id [String] The distinct id of the user.
|
|
352
|
+
# @param groups [Hash] Group analytics mapping from group type to group key.
|
|
353
|
+
# @param person_properties [Hash] Properties to use when evaluating the user locally or remotely.
|
|
354
|
+
# @param group_properties [Hash] Properties to use when evaluating groups locally or remotely.
|
|
355
|
+
# @param only_evaluate_locally [Boolean] Skip the remote /flags call.
|
|
356
|
+
# @param send_feature_flag_events [Boolean] Whether to capture `$feature_flag_called` for this access.
|
|
357
|
+
# @return [Boolean, nil] Whether the flag is enabled, or nil when the flag could not be evaluated.
|
|
338
358
|
# TODO: In future version, rename to `feature_flag_enabled?`
|
|
339
359
|
def is_feature_enabled( # rubocop:disable Naming/PredicateName
|
|
340
360
|
flag_key,
|
|
@@ -364,8 +384,8 @@ module PostHog
|
|
|
364
384
|
!!response
|
|
365
385
|
end
|
|
366
386
|
|
|
367
|
-
# @param [String, Symbol]
|
|
368
|
-
# @return [
|
|
387
|
+
# @param flag_key [String, Symbol] The unique flag key of the remote config feature flag.
|
|
388
|
+
# @return [Hash] The parsed remote config payload response.
|
|
369
389
|
def get_remote_config_payload(flag_key)
|
|
370
390
|
@feature_flags_poller.get_remote_config_payload(flag_key.to_s)
|
|
371
391
|
end
|
|
@@ -377,8 +397,10 @@ module PostHog
|
|
|
377
397
|
# @param [Hash] groups
|
|
378
398
|
# @param [Hash] person_properties key-value pairs of properties to associate with the user.
|
|
379
399
|
# @param [Hash] group_properties
|
|
400
|
+
# @param only_evaluate_locally [Boolean] Skip the remote /flags call.
|
|
401
|
+
# @param send_feature_flag_events [Boolean] Whether to capture `$feature_flag_called` for this access.
|
|
380
402
|
#
|
|
381
|
-
# @return [String, nil] The value of the feature flag
|
|
403
|
+
# @return [String, Boolean, nil] The value of the feature flag
|
|
382
404
|
#
|
|
383
405
|
# The provided properties are used to calculate feature flags locally, if possible.
|
|
384
406
|
#
|
|
@@ -418,6 +440,14 @@ module PostHog
|
|
|
418
440
|
|
|
419
441
|
# @deprecated Use {#evaluate_flags} and {FeatureFlagEvaluations#get_flag} /
|
|
420
442
|
# {FeatureFlagEvaluations#get_flag_payload} instead.
|
|
443
|
+
# @param key [String, Symbol] The unique key of the feature flag.
|
|
444
|
+
# @param distinct_id [String] The distinct id of the user.
|
|
445
|
+
# @param groups [Hash] Group analytics mapping from group type to group key.
|
|
446
|
+
# @param person_properties [Hash] Properties to use when evaluating the user locally or remotely.
|
|
447
|
+
# @param group_properties [Hash] Properties to use when evaluating groups locally or remotely.
|
|
448
|
+
# @param only_evaluate_locally [Boolean] Skip the remote /flags call.
|
|
449
|
+
# @param send_feature_flag_events [Boolean] Whether to capture `$feature_flag_called` for this access.
|
|
450
|
+
# @return [PostHog::FeatureFlagResult, nil]
|
|
421
451
|
def get_feature_flag_result(
|
|
422
452
|
key,
|
|
423
453
|
distinct_id,
|
|
@@ -454,7 +484,8 @@ module PostHog
|
|
|
454
484
|
# @param [Hash] person_properties key-value pairs of properties to associate with the user
|
|
455
485
|
# @param [Hash] group_properties
|
|
456
486
|
# @param [Boolean] only_evaluate_locally Skip the remote /flags call entirely
|
|
457
|
-
# @param [Boolean] disable_geoip
|
|
487
|
+
# @param [Boolean, nil] disable_geoip When true, disables GeoIP lookup for remote evaluation and stamps captured
|
|
488
|
+
# access events.
|
|
458
489
|
# @param [Array<String, Symbol>] flag_keys When set, scopes the underlying /flags
|
|
459
490
|
# request to only these flag keys (sent as `flag_keys_to_evaluate`).
|
|
460
491
|
# Distinct from {FeatureFlagEvaluations#only}, which filters the
|
|
@@ -578,6 +609,7 @@ module PostHog
|
|
|
578
609
|
# @param [Hash] groups
|
|
579
610
|
# @param [Hash] person_properties key-value pairs of properties to associate with the user.
|
|
580
611
|
# @param [Hash] group_properties
|
|
612
|
+
# @param only_evaluate_locally [Boolean] Skip the remote /flags call.
|
|
581
613
|
#
|
|
582
614
|
# @return [Hash] String (not symbol) key value pairs of flag and their values
|
|
583
615
|
def get_all_flags(
|
|
@@ -600,11 +632,12 @@ module PostHog
|
|
|
600
632
|
#
|
|
601
633
|
# @param [String, Symbol] key The key of the feature flag
|
|
602
634
|
# @param [String] distinct_id The distinct id of the user
|
|
603
|
-
# @
|
|
604
|
-
# @
|
|
605
|
-
# @
|
|
606
|
-
# @
|
|
607
|
-
# @
|
|
635
|
+
# @param match_value [String, Boolean, nil] The value of the feature flag to be matched
|
|
636
|
+
# @param groups [Hash]
|
|
637
|
+
# @param person_properties [Hash] key-value pairs of properties to associate with the user.
|
|
638
|
+
# @param group_properties [Hash]
|
|
639
|
+
# @param only_evaluate_locally [Boolean]
|
|
640
|
+
# @return [Object, nil] The parsed payload for the matched flag value.
|
|
608
641
|
#
|
|
609
642
|
# @deprecated Use {#evaluate_flags} and {FeatureFlagEvaluations#get_flag_payload} instead.
|
|
610
643
|
def get_feature_flag_payload(
|
|
@@ -637,10 +670,10 @@ module PostHog
|
|
|
637
670
|
# featureFlagPayloads: A hash of feature flag payloads
|
|
638
671
|
#
|
|
639
672
|
# @param [String] distinct_id The distinct id of the user
|
|
640
|
-
# @
|
|
641
|
-
# @
|
|
642
|
-
# @
|
|
643
|
-
# @
|
|
673
|
+
# @param groups [Hash]
|
|
674
|
+
# @param person_properties [Hash] key-value pairs of properties to associate with the user.
|
|
675
|
+
# @param group_properties [Hash]
|
|
676
|
+
# @param only_evaluate_locally [Boolean] Skip the remote /flags call.
|
|
644
677
|
#
|
|
645
678
|
def get_all_flags_and_payloads(
|
|
646
679
|
distinct_id,
|
|
@@ -662,6 +695,9 @@ module PostHog
|
|
|
662
695
|
response
|
|
663
696
|
end
|
|
664
697
|
|
|
698
|
+
# Reload locally cached feature flag definitions.
|
|
699
|
+
#
|
|
700
|
+
# @return [void]
|
|
665
701
|
def reload_feature_flags
|
|
666
702
|
unless @personal_api_key
|
|
667
703
|
logger.error(
|
|
@@ -672,6 +708,9 @@ module PostHog
|
|
|
672
708
|
@feature_flags_poller.load_feature_flags(true)
|
|
673
709
|
end
|
|
674
710
|
|
|
711
|
+
# Flush pending events and stop background resources.
|
|
712
|
+
#
|
|
713
|
+
# @return [void]
|
|
675
714
|
def shutdown
|
|
676
715
|
self.class._decrement_instance_count(@api_key) if @api_key
|
|
677
716
|
@feature_flags_poller.shutdown_poller
|
|
@@ -685,6 +724,39 @@ module PostHog
|
|
|
685
724
|
|
|
686
725
|
private
|
|
687
726
|
|
|
727
|
+
def enrich_capture_attrs_with_context(attrs)
|
|
728
|
+
context = Internal::Context.current
|
|
729
|
+
explicit_properties = attrs[:properties]
|
|
730
|
+
properties_are_hash = explicit_properties.nil? || explicit_properties.is_a?(Hash)
|
|
731
|
+
context_properties = context&.properties || {}
|
|
732
|
+
if properties_are_hash
|
|
733
|
+
attrs[:properties] = Internal::Context.merge_properties(context_properties, explicit_properties || {})
|
|
734
|
+
end
|
|
735
|
+
|
|
736
|
+
return if present_id?(attrs[:distinct_id])
|
|
737
|
+
|
|
738
|
+
if present_id?(context&.distinct_id)
|
|
739
|
+
attrs[:distinct_id] = context.distinct_id
|
|
740
|
+
return
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
attrs[:distinct_id] = SecureRandom.uuid
|
|
744
|
+
return unless properties_are_hash
|
|
745
|
+
return if property_key?(explicit_properties, '$process_person_profile')
|
|
746
|
+
|
|
747
|
+
attrs[:properties]['$process_person_profile'] = false
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
def present_id?(value)
|
|
751
|
+
!(value.nil? || (value.is_a?(String) && value.empty?))
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
def property_key?(properties, key)
|
|
755
|
+
return false unless properties.is_a?(Hash)
|
|
756
|
+
|
|
757
|
+
properties.key?(key) || properties.key?(key.to_sym)
|
|
758
|
+
end
|
|
759
|
+
|
|
688
760
|
# Shared by the legacy single-flag path ({#get_feature_flag_result}) and the
|
|
689
761
|
# snapshot's access-recording. Owns dedup-key construction, the
|
|
690
762
|
# per-distinct_id sent-flags cache, and the `$feature_flag_called` capture call.
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
# 💖 open source (under MIT License)
|
|
12
12
|
|
|
13
13
|
module PostHog
|
|
14
|
+
# Builds PostHog exception payloads from Ruby exception objects.
|
|
15
|
+
#
|
|
16
|
+
# @api private
|
|
14
17
|
module ExceptionCapture
|
|
15
18
|
RUBY_INPUT_FORMAT = /
|
|
16
19
|
^ \s* (?: [a-zA-Z]: | uri:classloader: )? ([^:]+ | <.*>):
|
|
@@ -18,6 +21,8 @@ module PostHog
|
|
|
18
21
|
(?: :in\s('|`)(?:([\w:]+)\#)?([^']+)')?$
|
|
19
22
|
/x
|
|
20
23
|
|
|
24
|
+
# @param value [Exception, String, Object] Exception input to parse.
|
|
25
|
+
# @return [Hash, nil] Parsed exception payload, or nil when the input is unsupported.
|
|
21
26
|
def self.build_parsed_exception(value)
|
|
22
27
|
title, message, backtrace = coerce_exception_input(value)
|
|
23
28
|
return nil if title.nil?
|
|
@@ -25,6 +30,10 @@ module PostHog
|
|
|
25
30
|
build_single_exception_from_data(title, message, backtrace)
|
|
26
31
|
end
|
|
27
32
|
|
|
33
|
+
# @param title [String]
|
|
34
|
+
# @param message [String, nil]
|
|
35
|
+
# @param backtrace [Array<String>, nil]
|
|
36
|
+
# @return [Hash]
|
|
28
37
|
def self.build_single_exception_from_data(title, message, backtrace)
|
|
29
38
|
{
|
|
30
39
|
'type' => title,
|
|
@@ -37,6 +46,8 @@ module PostHog
|
|
|
37
46
|
}
|
|
38
47
|
end
|
|
39
48
|
|
|
49
|
+
# @param backtrace [Array<String>, nil]
|
|
50
|
+
# @return [Hash, nil]
|
|
40
51
|
def self.build_stacktrace(backtrace)
|
|
41
52
|
return nil unless backtrace && !backtrace.empty?
|
|
42
53
|
|
|
@@ -50,6 +61,8 @@ module PostHog
|
|
|
50
61
|
}
|
|
51
62
|
end
|
|
52
63
|
|
|
64
|
+
# @param line [String]
|
|
65
|
+
# @return [Hash, nil]
|
|
53
66
|
def self.parse_backtrace_line(line)
|
|
54
67
|
match = line.match(RUBY_INPUT_FORMAT)
|
|
55
68
|
return nil unless match
|
|
@@ -72,6 +85,8 @@ module PostHog
|
|
|
72
85
|
frame
|
|
73
86
|
end
|
|
74
87
|
|
|
88
|
+
# @param path [String]
|
|
89
|
+
# @return [Boolean]
|
|
75
90
|
def self.gem_path?(path)
|
|
76
91
|
path.include?('/gems/') ||
|
|
77
92
|
path.include?('/ruby/') ||
|
|
@@ -79,6 +94,11 @@ module PostHog
|
|
|
79
94
|
path.include?('/.rvm/')
|
|
80
95
|
end
|
|
81
96
|
|
|
97
|
+
# @param frame [Hash]
|
|
98
|
+
# @param file_path [String]
|
|
99
|
+
# @param lineno [Integer]
|
|
100
|
+
# @param context_size [Integer]
|
|
101
|
+
# @return [void]
|
|
82
102
|
def self.add_context_lines(frame, file_path, lineno, context_size = 5)
|
|
83
103
|
lines = File.readlines(file_path)
|
|
84
104
|
return if lines.empty?
|
|
@@ -97,6 +117,8 @@ module PostHog
|
|
|
97
117
|
# Silently ignore file read errors
|
|
98
118
|
end
|
|
99
119
|
|
|
120
|
+
# @param value [Exception, String, Object]
|
|
121
|
+
# @return [Array] Three-item array of title, message, and backtrace.
|
|
100
122
|
def self.coerce_exception_input(value)
|
|
101
123
|
if value.is_a?(String)
|
|
102
124
|
title = 'Error'
|
data/lib/posthog/feature_flag.rb
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Represents a feature flag returned by /flags v2
|
|
4
3
|
module PostHog
|
|
4
|
+
# Represents a feature flag returned by /flags v2.
|
|
5
|
+
#
|
|
6
|
+
# @api private
|
|
5
7
|
class FeatureFlag
|
|
6
8
|
attr_reader :key, :enabled, :variant, :reason, :metadata, :failed
|
|
7
9
|
|
|
10
|
+
# @param json [Hash] Raw feature flag data returned by /flags.
|
|
8
11
|
def initialize(json)
|
|
9
12
|
json.transform_keys!(&:to_s)
|
|
10
13
|
@key = json['key']
|
|
@@ -15,15 +18,21 @@ module PostHog
|
|
|
15
18
|
@failed = json['failed']
|
|
16
19
|
end
|
|
17
20
|
|
|
21
|
+
# @return [String, Boolean] The variant value when present, otherwise the enabled status.
|
|
18
22
|
# TODO: Rename to `value` in future version
|
|
19
23
|
def get_value # rubocop:disable Naming/AccessorMethodName
|
|
20
24
|
@variant || @enabled
|
|
21
25
|
end
|
|
22
26
|
|
|
27
|
+
# @return [Object, nil] The flag payload from metadata.
|
|
23
28
|
def payload
|
|
24
29
|
@metadata&.payload
|
|
25
30
|
end
|
|
26
31
|
|
|
32
|
+
# @param key [String, Symbol] The feature flag key.
|
|
33
|
+
# @param value [String, Boolean] The feature flag value.
|
|
34
|
+
# @param payload [Object, nil] The feature flag payload.
|
|
35
|
+
# @return [PostHog::FeatureFlag]
|
|
27
36
|
def self.from_value_and_payload(key, value, payload)
|
|
28
37
|
new({
|
|
29
38
|
'key' => key,
|
|
@@ -40,10 +49,13 @@ module PostHog
|
|
|
40
49
|
end
|
|
41
50
|
end
|
|
42
51
|
|
|
43
|
-
# Represents the reason why a flag was enabled/disabled
|
|
52
|
+
# Represents the reason why a flag was enabled/disabled.
|
|
53
|
+
#
|
|
54
|
+
# @api private
|
|
44
55
|
class EvaluationReason
|
|
45
56
|
attr_reader :code, :description, :condition_index
|
|
46
57
|
|
|
58
|
+
# @param json [Hash] Raw reason data returned by /flags.
|
|
47
59
|
def initialize(json)
|
|
48
60
|
json.transform_keys!(&:to_s)
|
|
49
61
|
@code = json['code']
|
|
@@ -52,10 +64,13 @@ module PostHog
|
|
|
52
64
|
end
|
|
53
65
|
end
|
|
54
66
|
|
|
55
|
-
# Represents metadata about a feature flag
|
|
67
|
+
# Represents metadata about a feature flag.
|
|
68
|
+
#
|
|
69
|
+
# @api private
|
|
56
70
|
class FeatureFlagMetadata
|
|
57
71
|
attr_reader :id, :version, :payload, :description
|
|
58
72
|
|
|
73
|
+
# @param json [Hash] Raw metadata returned by /flags.
|
|
59
74
|
def initialize(json)
|
|
60
75
|
json.transform_keys!(&:to_s)
|
|
61
76
|
@id = json['id']
|
|
@@ -17,6 +17,7 @@ module PostHog
|
|
|
17
17
|
#
|
|
18
18
|
# For API errors with status codes, use the api_error() method which returns
|
|
19
19
|
# a string like "api_error_500".
|
|
20
|
+
# @api private
|
|
20
21
|
class FeatureFlagError
|
|
21
22
|
ERRORS_WHILE_COMPUTING = 'errors_while_computing_flags'
|
|
22
23
|
FLAG_MISSING = 'flag_missing'
|
|
@@ -4,7 +4,7 @@ require 'set'
|
|
|
4
4
|
|
|
5
5
|
module PostHog
|
|
6
6
|
# A snapshot of feature flag evaluations for one distinct_id, returned by
|
|
7
|
-
# PostHog::Client#evaluate_flags. Calls to {#
|
|
7
|
+
# PostHog::Client#evaluate_flags. Calls to {#enabled?} / {#get_flag} fire the
|
|
8
8
|
# `$feature_flag_called` event (deduped through the existing per-distinct_id
|
|
9
9
|
# cache); {#get_flag_payload} does not. Pass the snapshot to `capture(flags:)`
|
|
10
10
|
# to attach `$feature/<key>` and `$active_feature_flags` without a second
|
|
@@ -19,8 +19,32 @@ module PostHog
|
|
|
19
19
|
|
|
20
20
|
Host = Struct.new(:capture_flag_called_event_if_needed, :log_warning, keyword_init: true)
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
# @return [String] The distinct id these evaluations belong to.
|
|
23
|
+
attr_reader :distinct_id
|
|
23
24
|
|
|
25
|
+
# @return [Hash, nil] Group analytics mapping used for evaluation.
|
|
26
|
+
attr_reader :groups
|
|
27
|
+
|
|
28
|
+
# @return [String, nil] Request id returned by the /flags endpoint.
|
|
29
|
+
attr_reader :request_id
|
|
30
|
+
|
|
31
|
+
# @return [String, nil] Evaluation timestamp returned by the /flags endpoint.
|
|
32
|
+
attr_reader :evaluated_at
|
|
33
|
+
|
|
34
|
+
# @return [Time, nil] When local flag definitions were loaded.
|
|
35
|
+
attr_reader :flag_definitions_loaded_at
|
|
36
|
+
|
|
37
|
+
# @param host [Host, nil] Internal host callbacks used to record flag access events.
|
|
38
|
+
# @param distinct_id [String, nil] The distinct id these evaluations belong to.
|
|
39
|
+
# @param flags [Hash] Evaluated flags keyed by flag key.
|
|
40
|
+
# @param groups [Hash, nil] Group analytics mapping from group type to group key.
|
|
41
|
+
# @param disable_geoip [Boolean, nil] Whether GeoIP was disabled during evaluation.
|
|
42
|
+
# @param request_id [String, nil] The request id returned by the /flags endpoint.
|
|
43
|
+
# @param evaluated_at [String, nil] The evaluation timestamp returned by the /flags endpoint.
|
|
44
|
+
# @param flag_definitions_loaded_at [Time, nil] When local flag definitions were loaded.
|
|
45
|
+
# @param errors_while_computing [Boolean] Whether the server reported errors while computing flags.
|
|
46
|
+
# @param quota_limited [Boolean] Whether feature flag evaluation was quota limited.
|
|
47
|
+
# @param accessed [Array<String>, Set<String>, nil] Flag keys already accessed by this snapshot.
|
|
24
48
|
def initialize(
|
|
25
49
|
host: nil,
|
|
26
50
|
distinct_id: nil,
|
|
@@ -47,10 +71,13 @@ module PostHog
|
|
|
47
71
|
@accessed = Set.new(accessed || [])
|
|
48
72
|
end
|
|
49
73
|
|
|
74
|
+
# @return [Array<String>] The evaluated flag keys in this snapshot.
|
|
50
75
|
def keys
|
|
51
76
|
@flags.keys
|
|
52
77
|
end
|
|
53
78
|
|
|
79
|
+
# @param key [String, Symbol] The feature flag key.
|
|
80
|
+
# @return [Boolean] true when the flag is enabled, false when disabled or missing.
|
|
54
81
|
def enabled?(key)
|
|
55
82
|
key = key.to_s
|
|
56
83
|
flag = @flags[key]
|
|
@@ -58,6 +85,9 @@ module PostHog
|
|
|
58
85
|
flag&.enabled ? true : false
|
|
59
86
|
end
|
|
60
87
|
|
|
88
|
+
# @param key [String, Symbol] The feature flag key.
|
|
89
|
+
# @return [String, Boolean, nil] Variant string for multivariate flags, true/false for boolean flags,
|
|
90
|
+
# or nil when the flag was not returned by the evaluation.
|
|
61
91
|
def get_flag(key)
|
|
62
92
|
key = key.to_s
|
|
63
93
|
flag = @flags[key]
|
|
@@ -65,6 +95,8 @@ module PostHog
|
|
|
65
95
|
_flag_value(flag)
|
|
66
96
|
end
|
|
67
97
|
|
|
98
|
+
# @param key [String, Symbol] The feature flag key.
|
|
99
|
+
# @return [Object, nil] The parsed payload for the flag, if any.
|
|
68
100
|
def get_flag_payload(key)
|
|
69
101
|
flag = @flags[key.to_s]
|
|
70
102
|
flag&.payload
|
|
@@ -73,10 +105,14 @@ module PostHog
|
|
|
73
105
|
# Order-dependent: if nothing has been accessed yet, the returned snapshot is
|
|
74
106
|
# empty. The method honors its name — pre-access flags before calling this if
|
|
75
107
|
# you want a populated result.
|
|
108
|
+
# @return [PostHog::FeatureFlagEvaluations] A snapshot containing only flags already accessed with
|
|
109
|
+
# {#enabled?} or {#get_flag}.
|
|
76
110
|
def only_accessed
|
|
77
111
|
_clone_with(@flags.slice(*@accessed))
|
|
78
112
|
end
|
|
79
113
|
|
|
114
|
+
# @param keys [Array<String, Symbol>, String, Symbol] Flag keys to keep in the returned snapshot.
|
|
115
|
+
# @return [PostHog::FeatureFlagEvaluations] A snapshot containing only the requested keys that exist.
|
|
80
116
|
def only(keys)
|
|
81
117
|
keys = Array(keys).map(&:to_s)
|
|
82
118
|
missing = keys.reject { |k| @flags.key?(k) }
|
|
@@ -92,6 +128,9 @@ module PostHog
|
|
|
92
128
|
|
|
93
129
|
# Builds the `$feature/<key>` and `$active_feature_flags` properties for a
|
|
94
130
|
# captured event. Called from PostHog::Client#capture when `flags:` is set.
|
|
131
|
+
#
|
|
132
|
+
# @api private
|
|
133
|
+
# @return [Hash]
|
|
95
134
|
def _get_event_properties
|
|
96
135
|
properties = {}
|
|
97
136
|
active = []
|
|
@@ -6,8 +6,19 @@ module PostHog
|
|
|
6
6
|
# Represents the result of a feature flag evaluation
|
|
7
7
|
# containing both the flag value and payload
|
|
8
8
|
class FeatureFlagResult
|
|
9
|
-
|
|
9
|
+
# @return [String, Symbol] The feature flag key.
|
|
10
|
+
attr_reader :key
|
|
10
11
|
|
|
12
|
+
# @return [String, nil] The variant key for multivariate flags.
|
|
13
|
+
attr_reader :variant
|
|
14
|
+
|
|
15
|
+
# @return [Object, nil] The parsed feature flag payload.
|
|
16
|
+
attr_reader :payload
|
|
17
|
+
|
|
18
|
+
# @param key [String, Symbol] The feature flag key.
|
|
19
|
+
# @param enabled [Boolean] Whether the feature flag is enabled.
|
|
20
|
+
# @param variant [String, nil] The variant key for multivariate flags.
|
|
21
|
+
# @param payload [Object, nil] The parsed feature flag payload.
|
|
11
22
|
def initialize(key:, enabled:, variant: nil, payload: nil)
|
|
12
23
|
@key = key
|
|
13
24
|
@enabled = enabled
|
|
@@ -15,18 +26,27 @@ module PostHog
|
|
|
15
26
|
@payload = payload
|
|
16
27
|
end
|
|
17
28
|
|
|
18
|
-
# Returns the effective value of the feature flag
|
|
19
|
-
#
|
|
29
|
+
# Returns the effective value of the feature flag: variant if present,
|
|
30
|
+
# otherwise enabled status.
|
|
31
|
+
#
|
|
32
|
+
# @return [String, Boolean]
|
|
20
33
|
def value
|
|
21
34
|
@variant || @enabled
|
|
22
35
|
end
|
|
23
36
|
|
|
24
|
-
# Returns whether or not the feature flag evaluated as enabled
|
|
37
|
+
# Returns whether or not the feature flag evaluated as enabled.
|
|
38
|
+
#
|
|
39
|
+
# @return [Boolean]
|
|
25
40
|
def enabled?
|
|
26
41
|
@enabled
|
|
27
42
|
end
|
|
28
43
|
|
|
29
|
-
# Factory method to create from flag value and payload
|
|
44
|
+
# Factory method to create from flag value and payload.
|
|
45
|
+
#
|
|
46
|
+
# @param key [String, Symbol] The feature flag key.
|
|
47
|
+
# @param value [String, Boolean, nil] The raw feature flag value.
|
|
48
|
+
# @param payload [Object, String, nil] The raw or JSON-encoded feature flag payload.
|
|
49
|
+
# @return [PostHog::FeatureFlagResult, nil]
|
|
30
50
|
def self.from_value_and_payload(key, value, payload)
|
|
31
51
|
return nil if value.nil?
|
|
32
52
|
|
|
@@ -43,6 +63,9 @@ module PostHog
|
|
|
43
63
|
# returned when the body is not valid JSON); already-deserialized values
|
|
44
64
|
# pass through. Public so {FeatureFlagEvaluations} can normalize payloads
|
|
45
65
|
# the same way {FeatureFlagResult} does.
|
|
66
|
+
#
|
|
67
|
+
# @param payload [Object, String, nil] The raw payload value.
|
|
68
|
+
# @return [Object, nil] The parsed payload.
|
|
46
69
|
def self.parse_payload(payload)
|
|
47
70
|
return nil if payload.nil?
|
|
48
71
|
return payload unless payload.is_a?(String)
|
|
@@ -20,10 +20,20 @@ module PostHog
|
|
|
20
20
|
class RequiresServerEvaluation < StandardError
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
# Polls and evaluates feature flag definitions for {PostHog::Client}.
|
|
24
|
+
#
|
|
25
|
+
# @api private
|
|
23
26
|
class FeatureFlagsPoller
|
|
24
27
|
include PostHog::Logging
|
|
25
28
|
include PostHog::Utils
|
|
26
29
|
|
|
30
|
+
# @param polling_interval [Integer, nil] Seconds between local feature flag definition polls.
|
|
31
|
+
# @param personal_api_key [String, nil] Personal API key used to fetch local evaluation definitions.
|
|
32
|
+
# @param project_api_key [String] Project API key.
|
|
33
|
+
# @param host [String] PostHog API host URL.
|
|
34
|
+
# @param feature_flag_request_timeout_seconds [Integer] Timeout for feature flag requests.
|
|
35
|
+
# @param on_error [Proc, nil] Callback invoked as `on_error.call(status, error)`.
|
|
36
|
+
# @param flag_definition_cache_provider [Object, nil] Optional {FlagDefinitionCacheProvider} implementation.
|
|
27
37
|
def initialize(
|
|
28
38
|
polling_interval,
|
|
29
39
|
personal_api_key,
|
|
@@ -446,6 +456,16 @@ module PostHog
|
|
|
446
456
|
parsed_dt
|
|
447
457
|
end
|
|
448
458
|
|
|
459
|
+
# Parse a single semver numeric identifier, rejecting empty, non-digit, or
|
|
460
|
+
# leading-zero values per semver 2.0.0 §2.
|
|
461
|
+
def self.parse_semver_numeric(part)
|
|
462
|
+
raise InconclusiveMatchError, 'Invalid semver format' if part.nil? || part.empty? || part !~ /^\d+$/
|
|
463
|
+
# Semver 2.0.0 §2: numeric identifiers MUST NOT include leading zeros.
|
|
464
|
+
raise InconclusiveMatchError, 'Invalid semver format' if part.length > 1 && part[0] == '0'
|
|
465
|
+
|
|
466
|
+
part.to_i
|
|
467
|
+
end
|
|
468
|
+
|
|
449
469
|
# Parse a semver string into a comparable [major, minor, patch] integer array.
|
|
450
470
|
# Handles v-prefix, whitespace, pre-release suffixes. Defaults missing components to 0.
|
|
451
471
|
def self.parse_semver(value)
|
|
@@ -461,14 +481,9 @@ module PostHog
|
|
|
461
481
|
|
|
462
482
|
raise InconclusiveMatchError, 'Invalid semver format' if parts.empty? || parts[0].to_s.empty?
|
|
463
483
|
|
|
464
|
-
|
|
465
|
-
parts.
|
|
466
|
-
|
|
467
|
-
end
|
|
468
|
-
|
|
469
|
-
major = parts[0].to_i
|
|
470
|
-
minor = parts.length > 1 ? parts[1].to_i : 0
|
|
471
|
-
patch = parts.length > 2 ? parts[2].to_i : 0
|
|
484
|
+
major = parse_semver_numeric(parts[0])
|
|
485
|
+
minor = parts.length > 1 ? parse_semver_numeric(parts[1]) : 0
|
|
486
|
+
patch = parts.length > 2 ? parse_semver_numeric(parts[2]) : 0
|
|
472
487
|
|
|
473
488
|
[major, minor, patch]
|
|
474
489
|
end
|
|
@@ -525,20 +540,22 @@ module PostHog
|
|
|
525
540
|
|
|
526
541
|
raise InconclusiveMatchError, 'Invalid semver wildcard format' if parts.empty?
|
|
527
542
|
|
|
528
|
-
parts.
|
|
529
|
-
|
|
543
|
+
numeric = parts.map do |part|
|
|
544
|
+
parse_semver_numeric(part)
|
|
545
|
+
rescue InconclusiveMatchError
|
|
546
|
+
raise InconclusiveMatchError, 'Invalid semver wildcard format'
|
|
530
547
|
end
|
|
531
548
|
|
|
532
|
-
major =
|
|
533
|
-
case
|
|
549
|
+
major = numeric[0]
|
|
550
|
+
case numeric.length
|
|
534
551
|
when 1
|
|
535
552
|
[[major, 0, 0], [major + 1, 0, 0]]
|
|
536
553
|
when 2
|
|
537
|
-
minor =
|
|
554
|
+
minor = numeric[1]
|
|
538
555
|
[[major, minor, 0], [major, minor + 1, 0]]
|
|
539
556
|
else
|
|
540
|
-
minor =
|
|
541
|
-
patch =
|
|
557
|
+
minor = numeric[1]
|
|
558
|
+
patch = numeric[2]
|
|
542
559
|
[[major, minor, patch], [major, minor, patch + 1]]
|
|
543
560
|
end
|
|
544
561
|
end
|
data/lib/posthog/field_parser.rb
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
require 'posthog/logging'
|
|
4
4
|
|
|
5
5
|
module PostHog
|
|
6
|
+
# Converts public SDK method arguments into PostHog API event payloads.
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
6
9
|
class FieldParser
|
|
7
10
|
class << self
|
|
8
11
|
include PostHog::Utils
|
|
@@ -14,6 +17,8 @@ module PostHog
|
|
|
14
17
|
# - "properties"
|
|
15
18
|
# - "groups"
|
|
16
19
|
# - "uuid"
|
|
20
|
+
# @param fields [Hash]
|
|
21
|
+
# @return [Hash]
|
|
17
22
|
def parse_for_capture(fields)
|
|
18
23
|
common = parse_common_fields(fields)
|
|
19
24
|
|
|
@@ -45,6 +50,8 @@ module PostHog
|
|
|
45
50
|
# In addition to the common fields, identify accepts:
|
|
46
51
|
#
|
|
47
52
|
# - "properties"
|
|
53
|
+
# @param fields [Hash]
|
|
54
|
+
# @return [Hash]
|
|
48
55
|
def parse_for_identify(fields)
|
|
49
56
|
common = parse_common_fields(fields)
|
|
50
57
|
|
|
@@ -63,6 +70,8 @@ module PostHog
|
|
|
63
70
|
)
|
|
64
71
|
end
|
|
65
72
|
|
|
73
|
+
# @param fields [Hash]
|
|
74
|
+
# @return [Hash]
|
|
66
75
|
def parse_for_group_identify(fields)
|
|
67
76
|
properties = fields[:properties] || {}
|
|
68
77
|
group_type = fields[:group_type]
|
|
@@ -92,6 +101,8 @@ module PostHog
|
|
|
92
101
|
# In addition to the common fields, alias accepts:
|
|
93
102
|
#
|
|
94
103
|
# - "alias"
|
|
104
|
+
# @param fields [Hash]
|
|
105
|
+
# @return [Hash]
|
|
95
106
|
def parse_for_alias(fields)
|
|
96
107
|
common = parse_common_fields(fields)
|
|
97
108
|
|
|
@@ -14,26 +14,31 @@ module PostHog
|
|
|
14
14
|
#
|
|
15
15
|
# == Required Methods
|
|
16
16
|
#
|
|
17
|
-
#
|
|
17
|
+
# @!method flag_definitions
|
|
18
18
|
# Retrieve cached flag definitions. Return a Hash with +:flags+,
|
|
19
19
|
# +:group_type_mapping+, and +:cohorts+ keys, or +nil+ if the cache
|
|
20
20
|
# is empty. Returning +nil+ triggers an API fetch when no flags are
|
|
21
21
|
# loaded yet (emergency fallback).
|
|
22
|
+
# @return [Hash, nil]
|
|
22
23
|
#
|
|
23
|
-
#
|
|
24
|
+
# @!method should_fetch_flag_definitions?
|
|
24
25
|
# Return +true+ if this instance should fetch new definitions from the
|
|
25
26
|
# API, +false+ to read from cache instead. Use for distributed lock
|
|
26
27
|
# coordination so only one worker fetches at a time.
|
|
28
|
+
# @return [Boolean]
|
|
27
29
|
#
|
|
28
|
-
#
|
|
30
|
+
# @!method on_flag_definitions_received(data)
|
|
29
31
|
# Called after successfully fetching new definitions from the API.
|
|
30
32
|
# +data+ is a Hash with +:flags+, +:group_type_mapping+, and +:cohorts+
|
|
31
33
|
# keys (plain Ruby types, not Concurrent:: wrappers). Store it in your
|
|
32
34
|
# external cache.
|
|
35
|
+
# @param data [Hash]
|
|
36
|
+
# @return [void]
|
|
33
37
|
#
|
|
34
|
-
#
|
|
38
|
+
# @!method shutdown
|
|
35
39
|
# Called when the PostHog client shuts down. Release any distributed
|
|
36
40
|
# locks and clean up resources.
|
|
41
|
+
# @return [void]
|
|
37
42
|
#
|
|
38
43
|
# == Error Handling
|
|
39
44
|
#
|
|
@@ -66,6 +71,7 @@ module PostHog
|
|
|
66
71
|
#
|
|
67
72
|
# @param provider [Object] the cache provider to validate
|
|
68
73
|
# @raise [ArgumentError] if any required methods are missing
|
|
74
|
+
# @return [void]
|
|
69
75
|
def self.validate!(provider)
|
|
70
76
|
missing = REQUIRED_METHODS.reject { |m| provider.respond_to?(m) }
|
|
71
77
|
return if missing.empty?
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PostHog
|
|
4
|
+
module Internal
|
|
5
|
+
# Internal request/fiber-local context applied to capture calls.
|
|
6
|
+
# Uses Rails' isolated execution state when available, otherwise falls back
|
|
7
|
+
# to thread-local storage in the core SDK.
|
|
8
|
+
#
|
|
9
|
+
# This is intentionally not exposed as a public SDK API in Ruby yet. It exists
|
|
10
|
+
# to let framework integrations such as posthog-rails propagate request-scoped
|
|
11
|
+
# tracing headers to regular capture and exception events without making the
|
|
12
|
+
# server-side SDK globally stateful per user.
|
|
13
|
+
class Context
|
|
14
|
+
STORAGE_KEY = :posthog_context
|
|
15
|
+
|
|
16
|
+
attr_reader :distinct_id, :session_id, :properties
|
|
17
|
+
|
|
18
|
+
def initialize(distinct_id: nil, session_id: nil, properties: {})
|
|
19
|
+
@distinct_id = distinct_id
|
|
20
|
+
@session_id = session_id
|
|
21
|
+
@properties = properties ? properties.dup : {}
|
|
22
|
+
apply_session_property!
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.current
|
|
26
|
+
if defined?(ActiveSupport::IsolatedExecutionState)
|
|
27
|
+
ActiveSupport::IsolatedExecutionState[STORAGE_KEY]
|
|
28
|
+
else
|
|
29
|
+
Thread.current[STORAGE_KEY]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.current=(context)
|
|
34
|
+
if defined?(ActiveSupport::IsolatedExecutionState)
|
|
35
|
+
ActiveSupport::IsolatedExecutionState[STORAGE_KEY] = context
|
|
36
|
+
else
|
|
37
|
+
Thread.current[STORAGE_KEY] = context
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.with_context(data = nil, fresh: false, **kwargs)
|
|
42
|
+
previous_context = current
|
|
43
|
+
raise ArgumentError, 'with_context requires a block' unless block_given?
|
|
44
|
+
|
|
45
|
+
self.current = resolve(merge_data_and_kwargs(data, kwargs), previous_context, fresh: fresh)
|
|
46
|
+
yield
|
|
47
|
+
ensure
|
|
48
|
+
self.current = previous_context
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.resolve(data, parent, fresh: false)
|
|
52
|
+
data = normalize_data(data)
|
|
53
|
+
|
|
54
|
+
parent_properties = fresh || parent.nil? ? {} : parent.properties
|
|
55
|
+
properties = merge_properties(parent_properties, data[:properties] || {})
|
|
56
|
+
if data[:session_id] && !session_property_key?(data[:properties])
|
|
57
|
+
properties.delete('$session_id')
|
|
58
|
+
properties.delete(:$session_id)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
new(
|
|
62
|
+
distinct_id: data[:distinct_id] || (fresh || parent.nil? ? nil : parent.distinct_id),
|
|
63
|
+
session_id: data[:session_id] || (fresh || parent.nil? ? nil : parent.session_id),
|
|
64
|
+
properties: properties
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
private_class_method :resolve
|
|
68
|
+
|
|
69
|
+
def self.merge_data_and_kwargs(data, kwargs)
|
|
70
|
+
data ||= {}
|
|
71
|
+
raise ArgumentError, 'context data must be a Hash' unless data.is_a?(Hash)
|
|
72
|
+
|
|
73
|
+
data.merge(kwargs)
|
|
74
|
+
end
|
|
75
|
+
private_class_method :merge_data_and_kwargs
|
|
76
|
+
|
|
77
|
+
def self.merge_properties(base, overrides)
|
|
78
|
+
merged = (base || {}).dup
|
|
79
|
+
(overrides || {}).each do |key, value|
|
|
80
|
+
merged.delete(key.to_s) if key.is_a?(Symbol)
|
|
81
|
+
merged.delete(key.to_sym) if key.is_a?(String)
|
|
82
|
+
merged[key] = value
|
|
83
|
+
end
|
|
84
|
+
merged
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def self.normalize_data(data)
|
|
88
|
+
data ||= {}
|
|
89
|
+
raise ArgumentError, 'context data must be a Hash' unless data.is_a?(Hash)
|
|
90
|
+
|
|
91
|
+
properties = data[:properties] || data['properties'] || {}
|
|
92
|
+
raise ArgumentError, 'context properties must be a Hash' unless properties.is_a?(Hash)
|
|
93
|
+
|
|
94
|
+
{
|
|
95
|
+
distinct_id: data[:distinct_id] || data['distinct_id'] || data[:distinctId] || data['distinctId'],
|
|
96
|
+
session_id: data[:session_id] || data['session_id'] || data[:sessionId] || data['sessionId'],
|
|
97
|
+
properties: properties
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
private_class_method :normalize_data
|
|
101
|
+
|
|
102
|
+
def self.session_property_key?(properties)
|
|
103
|
+
return false unless properties.is_a?(Hash)
|
|
104
|
+
|
|
105
|
+
properties.key?('$session_id') || properties.key?(:$session_id)
|
|
106
|
+
end
|
|
107
|
+
private_class_method :session_property_key?
|
|
108
|
+
|
|
109
|
+
def apply_session_property!
|
|
110
|
+
return if session_id.nil? || properties.key?('$session_id') || properties.key?(:$session_id)
|
|
111
|
+
|
|
112
|
+
properties['$session_id'] = session_id
|
|
113
|
+
end
|
|
114
|
+
private :apply_session_property!
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private_constant :Internal
|
|
119
|
+
end
|
data/lib/posthog/logging.rb
CHANGED
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
require 'logger'
|
|
4
4
|
|
|
5
5
|
module PostHog
|
|
6
|
-
# Wraps an existing logger and adds a prefix to all messages
|
|
6
|
+
# Wraps an existing logger and adds a prefix to all messages.
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
7
9
|
class PrefixedLogger
|
|
10
|
+
# @param logger [Logger, #debug, #info, #warn, #error]
|
|
11
|
+
# @param prefix [String]
|
|
8
12
|
def initialize(logger, prefix)
|
|
9
13
|
@logger = logger
|
|
10
14
|
@prefix = prefix
|
|
@@ -37,6 +41,7 @@ module PostHog
|
|
|
37
41
|
|
|
38
42
|
module Logging
|
|
39
43
|
class << self
|
|
44
|
+
# @return [Logger, PostHog::PrefixedLogger] The logger used by the SDK.
|
|
40
45
|
def logger
|
|
41
46
|
return @logger if @logger
|
|
42
47
|
|
|
@@ -52,6 +57,7 @@ module PostHog
|
|
|
52
57
|
@logger = PrefixedLogger.new(base_logger, '[posthog-ruby]')
|
|
53
58
|
end
|
|
54
59
|
|
|
60
|
+
# @param logger [Logger, #debug, #info, #warn, #error] Custom logger used by the SDK.
|
|
55
61
|
attr_writer :logger
|
|
56
62
|
end
|
|
57
63
|
|
|
@@ -63,6 +69,7 @@ module PostHog
|
|
|
63
69
|
end
|
|
64
70
|
end
|
|
65
71
|
|
|
72
|
+
# @return [Logger, PostHog::PrefixedLogger] The logger used by the SDK.
|
|
66
73
|
def logger
|
|
67
74
|
Logging.logger
|
|
68
75
|
end
|
|
@@ -4,7 +4,9 @@ require 'forwardable'
|
|
|
4
4
|
require 'posthog/logging'
|
|
5
5
|
|
|
6
6
|
module PostHog
|
|
7
|
-
# A batch of
|
|
7
|
+
# A batch of messages to be sent to the API.
|
|
8
|
+
#
|
|
9
|
+
# @api private
|
|
8
10
|
class MessageBatch
|
|
9
11
|
class JSONGenerationError < StandardError
|
|
10
12
|
end
|
|
@@ -13,12 +15,15 @@ module PostHog
|
|
|
13
15
|
include PostHog::Logging
|
|
14
16
|
include PostHog::Defaults::MessageBatch
|
|
15
17
|
|
|
18
|
+
# @param max_message_count [Integer] Maximum number of messages in the batch.
|
|
16
19
|
def initialize(max_message_count)
|
|
17
20
|
@messages = []
|
|
18
21
|
@max_message_count = max_message_count
|
|
19
22
|
@json_size = 0
|
|
20
23
|
end
|
|
21
24
|
|
|
25
|
+
# @param message [Hash] Message to add to the batch.
|
|
26
|
+
# @return [Array<Hash>, nil]
|
|
22
27
|
def <<(message)
|
|
23
28
|
begin
|
|
24
29
|
message_json = message.to_json
|
|
@@ -35,10 +40,12 @@ module PostHog
|
|
|
35
40
|
end
|
|
36
41
|
end
|
|
37
42
|
|
|
43
|
+
# @return [Boolean] Whether the batch is full.
|
|
38
44
|
def full?
|
|
39
45
|
item_count_exhausted? || size_exhausted?
|
|
40
46
|
end
|
|
41
47
|
|
|
48
|
+
# @return [void]
|
|
42
49
|
def clear
|
|
43
50
|
@messages.clear
|
|
44
51
|
@json_size = 0
|
data/lib/posthog/noop_worker.rb
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# A worker that doesn't consume jobs
|
|
4
3
|
module PostHog
|
|
4
|
+
# A worker that doesn't consume jobs.
|
|
5
|
+
#
|
|
6
|
+
# @api private
|
|
5
7
|
class NoopWorker
|
|
8
|
+
# @param queue [Queue]
|
|
6
9
|
def initialize(queue)
|
|
7
10
|
@queue = queue
|
|
8
11
|
end
|
|
9
12
|
|
|
13
|
+
# @return [void]
|
|
10
14
|
def run
|
|
11
15
|
# Does nothing
|
|
12
16
|
end
|
|
13
17
|
|
|
18
|
+
# @return [Boolean]
|
|
14
19
|
# TODO: Rename to `requesting?` in future version
|
|
15
20
|
def is_requesting? # rubocop:disable Naming/PredicateName
|
|
16
21
|
false
|
|
17
22
|
end
|
|
18
23
|
|
|
24
|
+
# @return [void]
|
|
19
25
|
def shutdown
|
|
20
26
|
# Does nothing
|
|
21
27
|
end
|
data/lib/posthog/response.rb
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module PostHog
|
|
4
|
+
# API response wrapper returned by the SDK transport.
|
|
5
|
+
#
|
|
6
|
+
# @api private
|
|
4
7
|
class Response
|
|
5
8
|
attr_reader :status, :error
|
|
6
9
|
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
+
# @param status [Integer] HTTP status code, or -1 for SDK/transport errors.
|
|
11
|
+
# @param error [String, nil] Error message returned by the API or SDK.
|
|
10
12
|
def initialize(status = 200, error = nil)
|
|
11
13
|
@status = status
|
|
12
14
|
@error = error
|
|
@@ -3,16 +3,29 @@
|
|
|
3
3
|
require 'posthog/utils'
|
|
4
4
|
|
|
5
5
|
module PostHog
|
|
6
|
-
# Options for configuring feature flag behavior in capture calls
|
|
6
|
+
# Options for configuring deprecated feature flag behavior in capture calls.
|
|
7
|
+
#
|
|
8
|
+
# @deprecated Prefer passing a {PostHog::FeatureFlagEvaluations} snapshot to `capture(flags:)`.
|
|
7
9
|
class SendFeatureFlagsOptions
|
|
8
|
-
|
|
10
|
+
# @return [Boolean, nil] Whether remote feature flag evaluation should be skipped.
|
|
11
|
+
attr_reader :only_evaluate_locally
|
|
9
12
|
|
|
13
|
+
# @return [Hash] Person properties to use for feature flag evaluation.
|
|
14
|
+
attr_reader :person_properties
|
|
15
|
+
|
|
16
|
+
# @return [Hash] Group properties to use for feature flag evaluation.
|
|
17
|
+
attr_reader :group_properties
|
|
18
|
+
|
|
19
|
+
# @param only_evaluate_locally [Boolean, nil] Skip remote feature flag evaluation.
|
|
20
|
+
# @param person_properties [Hash, nil] Person properties to use for feature flag evaluation.
|
|
21
|
+
# @param group_properties [Hash, nil] Group properties to use for feature flag evaluation.
|
|
10
22
|
def initialize(only_evaluate_locally: nil, person_properties: nil, group_properties: nil)
|
|
11
23
|
@only_evaluate_locally = only_evaluate_locally
|
|
12
24
|
@person_properties = person_properties || {}
|
|
13
25
|
@group_properties = group_properties || {}
|
|
14
26
|
end
|
|
15
27
|
|
|
28
|
+
# @return [Hash] A hash representation suitable for `capture(send_feature_flags:)`.
|
|
16
29
|
def to_h
|
|
17
30
|
{
|
|
18
31
|
only_evaluate_locally: @only_evaluate_locally,
|
|
@@ -21,6 +34,8 @@ module PostHog
|
|
|
21
34
|
}
|
|
22
35
|
end
|
|
23
36
|
|
|
37
|
+
# @param hash [Hash]
|
|
38
|
+
# @return [PostHog::SendFeatureFlagsOptions, nil]
|
|
24
39
|
def self.from_hash(hash)
|
|
25
40
|
return nil unless hash.is_a?(Hash)
|
|
26
41
|
|
data/lib/posthog/send_worker.rb
CHANGED
|
@@ -6,6 +6,9 @@ require 'posthog/transport'
|
|
|
6
6
|
require 'posthog/utils'
|
|
7
7
|
|
|
8
8
|
module PostHog
|
|
9
|
+
# Background worker that batches and sends queued events.
|
|
10
|
+
#
|
|
11
|
+
# @api private
|
|
9
12
|
class SendWorker
|
|
10
13
|
include PostHog::Utils
|
|
11
14
|
include PostHog::Defaults
|
|
@@ -16,12 +19,13 @@ module PostHog
|
|
|
16
19
|
# The worker continuously takes messages off the queue
|
|
17
20
|
# and makes requests to the posthog.com api
|
|
18
21
|
#
|
|
19
|
-
# queue
|
|
20
|
-
# api_key
|
|
21
|
-
# options
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
22
|
+
# @param queue [Queue] Queue synchronized between client and worker.
|
|
23
|
+
# @param api_key [String] Project API key.
|
|
24
|
+
# @param options [Hash] Worker options.
|
|
25
|
+
# @option options [Integer] :batch_size How many items to send in a batch.
|
|
26
|
+
# @option options [Proc] :on_error Callback invoked as `on_error.call(status, error)`.
|
|
27
|
+
# @option options [String] :host PostHog API host URL.
|
|
28
|
+
# @option options [Boolean] :skip_ssl_verification Disable SSL certificate verification.
|
|
25
29
|
def initialize(queue, api_key, options = {})
|
|
26
30
|
symbolize_keys! options
|
|
27
31
|
@queue = queue
|
|
@@ -33,8 +37,9 @@ module PostHog
|
|
|
33
37
|
@transport = Transport.new api_host: options[:host], skip_ssl_verification: options[:skip_ssl_verification]
|
|
34
38
|
end
|
|
35
39
|
|
|
36
|
-
#
|
|
40
|
+
# Continuously runs the loop to check for new events.
|
|
37
41
|
#
|
|
42
|
+
# @return [void]
|
|
38
43
|
def run
|
|
39
44
|
until Thread.current[:should_exit]
|
|
40
45
|
return if @queue.empty?
|
|
@@ -54,12 +59,14 @@ module PostHog
|
|
|
54
59
|
@transport.shutdown
|
|
55
60
|
end
|
|
56
61
|
|
|
62
|
+
# @return [void]
|
|
57
63
|
def shutdown
|
|
58
64
|
@transport.shutdown
|
|
59
65
|
end
|
|
60
66
|
|
|
61
67
|
# public: Check whether we have outstanding requests.
|
|
62
68
|
#
|
|
69
|
+
# @return [Boolean] Whether the worker has outstanding requests.
|
|
63
70
|
# TODO: Rename to `requesting?` in future version
|
|
64
71
|
def is_requesting? # rubocop:disable Naming/PredicateName
|
|
65
72
|
@lock.synchronize { !@batch.empty? }
|
data/lib/posthog/transport.rb
CHANGED
|
@@ -10,11 +10,24 @@ require 'net/https'
|
|
|
10
10
|
require 'json'
|
|
11
11
|
|
|
12
12
|
module PostHog
|
|
13
|
+
# HTTP transport used by the SDK workers.
|
|
14
|
+
#
|
|
15
|
+
# @api private
|
|
13
16
|
class Transport
|
|
14
17
|
include PostHog::Defaults::Request
|
|
15
18
|
include PostHog::Utils
|
|
16
19
|
include PostHog::Logging
|
|
17
20
|
|
|
21
|
+
# @param options [Hash] Transport configuration.
|
|
22
|
+
# @option options [String] :api_host Full PostHog API host URL.
|
|
23
|
+
# @option options [String] :host Hostname to connect to.
|
|
24
|
+
# @option options [Integer] :port Port to connect to.
|
|
25
|
+
# @option options [Boolean] :ssl Whether to use HTTPS.
|
|
26
|
+
# @option options [Hash] :headers HTTP headers for batch requests.
|
|
27
|
+
# @option options [String] :path HTTP path for batch requests.
|
|
28
|
+
# @option options [Integer] :retries Number of retry attempts for retryable failures.
|
|
29
|
+
# @option options [PostHog::BackoffPolicy] :backoff_policy Backoff policy used between retries.
|
|
30
|
+
# @option options [Boolean] :skip_ssl_verification Disable SSL certificate verification.
|
|
18
31
|
def initialize(options = {})
|
|
19
32
|
if options[:api_host]
|
|
20
33
|
uri = URI.parse(options[:api_host])
|
|
@@ -43,6 +56,8 @@ module PostHog
|
|
|
43
56
|
|
|
44
57
|
# Sends a batch of messages to the API
|
|
45
58
|
#
|
|
59
|
+
# @param api_key [String] Project API key.
|
|
60
|
+
# @param batch [PostHog::MessageBatch, Array<Hash>] Batch of messages to send.
|
|
46
61
|
# @return [Response] API response
|
|
47
62
|
def send(api_key, batch)
|
|
48
63
|
logger.debug("Sending request for #{batch.length} items")
|
|
@@ -72,7 +87,9 @@ module PostHog
|
|
|
72
87
|
end
|
|
73
88
|
end
|
|
74
89
|
|
|
75
|
-
# Closes a persistent connection if it exists
|
|
90
|
+
# Closes a persistent connection if it exists.
|
|
91
|
+
#
|
|
92
|
+
# @return [void]
|
|
76
93
|
def shutdown
|
|
77
94
|
@http.finish if @http.started?
|
|
78
95
|
end
|
data/lib/posthog/utils.rb
CHANGED
|
@@ -6,6 +6,9 @@ module PostHog
|
|
|
6
6
|
class InconclusiveMatchError < StandardError
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
+
# Utility helpers used internally by the SDK.
|
|
10
|
+
#
|
|
11
|
+
# @api private
|
|
9
12
|
module Utils
|
|
10
13
|
module_function
|
|
11
14
|
|
|
@@ -145,12 +148,19 @@ module PostHog
|
|
|
145
148
|
end
|
|
146
149
|
end
|
|
147
150
|
|
|
151
|
+
# Hash that clears itself when it reaches a maximum length.
|
|
152
|
+
#
|
|
153
|
+
# @api private
|
|
148
154
|
class SizeLimitedHash < Hash
|
|
155
|
+
# @param max_length [Integer]
|
|
149
156
|
def initialize(max_length, ...)
|
|
150
157
|
super(...)
|
|
151
158
|
@max_length = max_length
|
|
152
159
|
end
|
|
153
160
|
|
|
161
|
+
# @param key [Object]
|
|
162
|
+
# @param value [Object]
|
|
163
|
+
# @return [Object]
|
|
154
164
|
def []=(key, value)
|
|
155
165
|
clear if length >= @max_length
|
|
156
166
|
super
|
data/lib/posthog/version.rb
CHANGED
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.
|
|
4
|
+
version: 3.9.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ''
|
|
@@ -46,6 +46,7 @@ files:
|
|
|
46
46
|
- lib/posthog/feature_flags.rb
|
|
47
47
|
- lib/posthog/field_parser.rb
|
|
48
48
|
- lib/posthog/flag_definition_cache.rb
|
|
49
|
+
- lib/posthog/internal/context.rb
|
|
49
50
|
- lib/posthog/logging.rb
|
|
50
51
|
- lib/posthog/message_batch.rb
|
|
51
52
|
- lib/posthog/noop_worker.rb
|