Class: PostHog::Client

Inherits:
Object
  • Object
show all
Includes:
Logging, Utils
Defined in:
lib/posthog/client.rb

Constant Summary

Constants included from Utils

Utils::UTC_OFFSET_WITHOUT_COLON, Utils::UTC_OFFSET_WITH_COLON

Instance Method Summary collapse

Methods included from Logging

included, #logger

Methods included from Utils

convert_to_datetime, date_in_iso8601, datetime_in_iso8601, formatted_offset, get_by_symbol_or_string_key, is_valid_regex, isoify_dates, isoify_dates!, seconds_to_utc_offset, stringify_keys, symbolize_keys, symbolize_keys!, time_in_iso8601, uid

Constructor Details

#initialize(opts = {}) ⇒ Client

Returns a new instance of Client.

Options Hash (opts):

  • :api_key (String)

    Your project’s api_key

  • :personal_api_key (String)

    Your personal API key

  • :max_queue_size (FixNum)

    Maximum number of calls to be remain queued. Defaults to 10_000.

  • :test_mode (Bool)

    true if messages should remain queued for testing. Defaults to false.

  • :on_error (Proc)

    Handles error calls from the API.

  • :host (String)

    Fully qualified hostname of the PostHog server. Defaults to ‘app.posthog.com`

  • :feature_flags_polling_interval (Integer)

    How often to poll for feature flag definition changes. Measured in seconds, defaults to 30.

  • :feature_flag_request_timeout_seconds (Integer)

    How long to wait for feature flag evaluation. Measured in seconds, defaults to 3.

  • :before_send (Proc)

    A block that receives the event hash and should return either a modified hash to be sent to PostHog or nil to prevent the event from being sent. e.g. ‘before_send: ->(event) { event }`



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/posthog/client.rb', line 35

def initialize(opts = {})
  symbolize_keys!(opts)

  opts[:host] ||= 'https://app.posthog.com'

  @queue = Queue.new
  @api_key = opts[:api_key]
  @max_queue_size = opts[:max_queue_size] || Defaults::Queue::MAX_SIZE
  @worker_mutex = Mutex.new
  @worker = if opts[:test_mode]
              NoopWorker.new(@queue)
            else
              SendWorker.new(@queue, @api_key, opts)
            end
  @worker_thread = nil
  @feature_flags_poller = nil
  @personal_api_key = opts[:personal_api_key]

  check_api_key!

  @feature_flags_poller =
    FeatureFlagsPoller.new(
      opts[:feature_flags_polling_interval],
      opts[:personal_api_key],
      @api_key,
      opts[:host],
      opts[:feature_flag_request_timeout_seconds] || Defaults::FeatureFlags::FLAG_REQUEST_TIMEOUT_SECONDS,
      opts[:on_error]
    )

  @distinct_id_has_sent_flag_calls = SizeLimitedHash.new(Defaults::MAX_HASH_SIZE) do |hash, key|
    hash[key] = []
  end

  @before_send = opts[:before_send]
end

Instance Method Details

#alias(attrs) ⇒ Object

Aliases a user from one id to another

Options Hash (attrs):

  • :alias (String)

    The alias to give the distinct id

  • :message_id (String)

    ID that uniquely identifies a message across the API. (optional)

  • :timestamp (Time)

    When the event occurred (optional)

  • :distinct_id (String)

    The ID for this user in your database



207
208
209
210
# File 'lib/posthog/client.rb', line 207

def alias(attrs)
  symbolize_keys! attrs
  enqueue(FieldParser.parse_for_alias(attrs))
end

#capture(attrs) ⇒ Object

Captures an event

Options Hash (attrs):

  • :event (String)

    Event name

  • :properties (Hash)

    Event properties (optional)

  • :send_feature_flags (Bool, Hash, SendFeatureFlagsOptions)

    Whether to send feature flags with this event, or configuration for feature flag evaluation (optional)

  • :uuid (String)

    ID that uniquely identifies an event; events in PostHog are deduplicated by the combination of teamId, timestamp date, event name, distinct id, and UUID

  • :message_id (String)

    ID that uniquely identifies a message across the API. (optional)

  • :timestamp (Time)

    When the event occurred (optional)

  • :distinct_id (String)

    The ID for this user in your database



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/posthog/client.rb', line 109

def capture(attrs)
  symbolize_keys! attrs

  send_feature_flags_param = attrs[:send_feature_flags]
  if send_feature_flags_param
    # Handle different types of send_feature_flags parameter
    case send_feature_flags_param
    when true
      # Backward compatibility: simple boolean
      feature_variants = @feature_flags_poller.get_feature_variants(attrs[:distinct_id], attrs[:groups] || {})
    when Hash
      # Hash with options
      options = SendFeatureFlagsOptions.from_hash(send_feature_flags_param)
      feature_variants = @feature_flags_poller.get_feature_variants(
        attrs[:distinct_id],
        attrs[:groups] || {},
        options ? options.person_properties : {},
        options ? options.group_properties : {},
        options ? options.only_evaluate_locally : false
      )
    when SendFeatureFlagsOptions
      # SendFeatureFlagsOptions object
      feature_variants = @feature_flags_poller.get_feature_variants(
        attrs[:distinct_id],
        attrs[:groups] || {},
        send_feature_flags_param.person_properties,
        send_feature_flags_param.group_properties,
        send_feature_flags_param.only_evaluate_locally || false
      )
    else
      # Invalid type, treat as false
      feature_variants = nil
    end

    attrs[:feature_variants] = feature_variants if feature_variants
  end

  enqueue(FieldParser.parse_for_capture(attrs))
end

#capture_exception(exception, distinct_id = nil, additional_properties = {}) ⇒ Object

Captures an exception as an event



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/posthog/client.rb', line 154

def capture_exception(exception, distinct_id = nil, additional_properties = {})
  exception_info = ExceptionCapture.build_parsed_exception(exception)

  return if exception_info.nil?

  no_distinct_id_was_provided = distinct_id.nil?
  distinct_id ||= SecureRandom.uuid

  properties = { '$exception_list' => [exception_info] }
  properties.merge!(additional_properties) if additional_properties && !additional_properties.empty?
  properties['$process_person_profile'] = false if no_distinct_id_was_provided

  event_data = {
    distinct_id: distinct_id,
    event: '$exception',
    properties: properties,
    timestamp: Time.now
  }

  capture(event_data)
end

#clearObject

Clears the queue without waiting.

Use only in test mode



86
87
88
# File 'lib/posthog/client.rb', line 86

def clear
  @queue.clear
end

#dequeue_last_messageHash



213
214
215
# File 'lib/posthog/client.rb', line 213

def dequeue_last_message
  @queue.pop
end

#flushObject

Synchronously waits until the worker has cleared the queue.

Use only for scripts which are not long-running, and will specifically exit



76
77
78
79
80
81
# File 'lib/posthog/client.rb', line 76

def flush
  while !@queue.empty? || @worker.is_requesting?
    ensure_worker_running
    sleep(0.1)
  end
end

#get_all_flags(distinct_id, groups: {}, person_properties: {}, group_properties: {}, only_evaluate_locally: false) ⇒ Hash

Returns all flags for a given user



324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/posthog/client.rb', line 324

def get_all_flags(
  distinct_id,
  groups: {},
  person_properties: {},
  group_properties: {},
  only_evaluate_locally: false
)
  person_properties, group_properties = add_local_person_and_group_properties(distinct_id, groups,
                                                                              person_properties, group_properties)
  @feature_flags_poller.get_all_flags(distinct_id, groups, person_properties, group_properties,
                                      only_evaluate_locally)
end

#get_all_flags_and_payloads(distinct_id, groups: {}, person_properties: {}, group_properties: {}, only_evaluate_locally: false) ⇒ Hash

Returns all flags and payloads for a given user



374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/posthog/client.rb', line 374

def get_all_flags_and_payloads(
  distinct_id,
  groups: {},
  person_properties: {},
  group_properties: {},
  only_evaluate_locally: false
)
  person_properties, group_properties = add_local_person_and_group_properties(
    distinct_id, groups, person_properties, group_properties
  )
  response = @feature_flags_poller.get_all_flags_and_payloads(
    distinct_id, groups, person_properties, group_properties, only_evaluate_locally
  )

  response.delete(:requestId) # remove internal information.
  response
end

#get_feature_flag(key, distinct_id, groups: {}, person_properties: {}, group_properties: {}, only_evaluate_locally: false, send_feature_flag_events: true) ⇒ String?

Returns whether the given feature flag is enabled for the given user or not

The provided properties are used to calculate feature flags locally, if possible.

‘groups` are a mapping from group type to group key. So, if you have a group type of “organization” and a group key of “5”, you would pass groups=“5”. `group_properties` take the format: { group_type_name: { group_properties } } So, for example, if you have the group type “organization” and the group key “5”, with the properties name, and employee count, you’ll send these as: “‘ruby

group_properties: {"organization": {"name": "PostHog", "employees": 11}}

“‘



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/posthog/client.rb', line 273

def get_feature_flag(
  key,
  distinct_id,
  groups: {},
  person_properties: {},
  group_properties: {},
  only_evaluate_locally: false,
  send_feature_flag_events: true
)
  person_properties, group_properties = add_local_person_and_group_properties(
    distinct_id,
    groups,
    person_properties,
    group_properties
  )
  feature_flag_response, flag_was_locally_evaluated, request_id = @feature_flags_poller.get_feature_flag(
    key,
    distinct_id,
    groups,
    person_properties,
    group_properties,
    only_evaluate_locally
  )

  feature_flag_reported_key = "#{key}_#{feature_flag_response}"
  if !@distinct_id_has_sent_flag_calls[distinct_id].include?(feature_flag_reported_key) && send_feature_flag_events
    capture(
      {
        distinct_id: distinct_id,
        event: '$feature_flag_called',
        properties: {
          '$feature_flag' => key,
          '$feature_flag_response' => feature_flag_response,
          'locally_evaluated' => flag_was_locally_evaluated
        }.merge(request_id ? { '$feature_flag_request_id' => request_id } : {}),
        groups: groups
      }
    )
    @distinct_id_has_sent_flag_calls[distinct_id] << feature_flag_reported_key
  end
  feature_flag_response
end

#get_feature_flag_payload(key, distinct_id, match_value: nil, groups: {}, person_properties: {}, group_properties: {}, only_evaluate_locally: false) ⇒ Object

Returns payload for a given feature flag



347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/posthog/client.rb', line 347

def get_feature_flag_payload(
  key,
  distinct_id,
  match_value: nil,
  groups: {},
  person_properties: {},
  group_properties: {},
  only_evaluate_locally: false
)
  person_properties, group_properties = add_local_person_and_group_properties(distinct_id, groups,
                                                                              person_properties, group_properties)
  @feature_flags_poller.get_feature_flag_payload(key, distinct_id, match_value, groups, person_properties,
                                                 group_properties, only_evaluate_locally)
end

#get_remote_config_payload(flag_key) ⇒ String



248
249
250
# File 'lib/posthog/client.rb', line 248

def get_remote_config_payload(flag_key)
  @feature_flags_poller.get_remote_config_payload(flag_key)
end

#group_identify(attrs) ⇒ Object

Identifies a group

Options Hash (attrs):

  • :group_type (String)

    Group type

  • :group_key (String)

    Group key

  • :properties (Hash)

    Group properties (optional)

  • :distinct_id (String)

    Distinct ID (optional)

  • :message_id (String)

    ID that uniquely identifies a message across the API. (optional)

  • :timestamp (Time)

    When the event occurred (optional)

  • :distinct_id (String)

    The ID for this user in your database



196
197
198
199
# File 'lib/posthog/client.rb', line 196

def group_identify(attrs)
  symbolize_keys! attrs
  enqueue(FieldParser.parse_for_group_identify(attrs))
end

#identify(attrs) ⇒ Object

Identifies a user

Options Hash (attrs):

  • :properties (Hash)

    User properties (optional)

  • :message_id (String)

    ID that uniquely identifies a message across the API. (optional)

  • :timestamp (Time)

    When the event occurred (optional)

  • :distinct_id (String)

    The ID for this user in your database



182
183
184
185
# File 'lib/posthog/client.rb', line 182

def identify(attrs)
  symbolize_keys! attrs
  enqueue(FieldParser.parse_for_identify(attrs))
end

#is_feature_enabled(flag_key, distinct_id, groups: {}, person_properties: {}, group_properties: {}, only_evaluate_locally: false, send_feature_flag_events: true) ⇒ Object

TODO: In future version, rename to ‘feature_flag_enabled?`



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/posthog/client.rb', line 223

def is_feature_enabled( # rubocop:disable Naming/PredicateName
  flag_key,
  distinct_id,
  groups: {},
  person_properties: {},
  group_properties: {},
  only_evaluate_locally: false,
  send_feature_flag_events: true
)
  response = get_feature_flag(
    flag_key,
    distinct_id,
    groups: groups,
    person_properties: person_properties,
    group_properties: group_properties,
    only_evaluate_locally: only_evaluate_locally,
    send_feature_flag_events: send_feature_flag_events
  )
  return nil if response.nil?

  !!response
end

#queued_messagesFixnum



218
219
220
# File 'lib/posthog/client.rb', line 218

def queued_messages
  @queue.length
end

#reload_feature_flagsObject



392
393
394
395
396
397
398
399
400
# File 'lib/posthog/client.rb', line 392

def reload_feature_flags
  unless @personal_api_key
    logger.error(
      'You need to specify a personal_api_key to locally evaluate feature flags'
    )
    return
  end
  @feature_flags_poller.load_feature_flags(true)
end

#shutdownObject



402
403
404
405
# File 'lib/posthog/client.rb', line 402

def shutdown
  @feature_flags_poller.shutdown_poller
  flush
end