Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/quiet-rabbits-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'posthog-ruby': patch
---

Generate SDK-created event and personless identifiers as UUID v7.
4 changes: 2 additions & 2 deletions lib/posthog/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

require 'time'
require 'json'
require 'securerandom'

require 'posthog/defaults'
require 'posthog/logging'
Expand All @@ -16,6 +15,7 @@
require 'posthog/send_feature_flags_options'
require 'posthog/exception_capture'
require 'posthog/internal/context'
require 'posthog/uuid'

module PostHog
class Client
Expand Down Expand Up @@ -810,7 +810,7 @@ def enrich_capture_attrs_with_context(attrs)
return
end

attrs[:distinct_id] = SecureRandom.uuid
attrs[:distinct_id] = PostHog::Uuid.v7
return unless properties_are_hash
return if property_key?(explicit_properties, '$process_person_profile')

Expand Down
5 changes: 2 additions & 3 deletions lib/posthog/field_parser.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# frozen_string_literal: true

require 'securerandom'

require 'posthog/logging'
require 'posthog/uuid'

module PostHog
# Converts public SDK method arguments into PostHog API event payloads.
Expand Down Expand Up @@ -196,7 +195,7 @@ def normalized_uuid(fields)
message_id = fields[:message_id]
return message_id if message_id && valid_uuid_for_event_props?(message_id)

SecureRandom.uuid
PostHog::Uuid.v7
end

# @param [Object] uuid - the UUID to validate, user provided, so we don't know the type
Expand Down
41 changes: 41 additions & 0 deletions lib/posthog/uuid.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

require 'securerandom'

module PostHog
# UUID generation helpers used by SDK-generated identifiers.
#
# @api private
module Uuid
NATIVE_UUID_V7 = SecureRandom.respond_to?(:uuid_v7)
private_constant :NATIVE_UUID_V7

module_function

def v7
return SecureRandom.uuid_v7 if NATIVE_UUID_V7

bytes = uuid_v7_bytes
hex = bytes.pack('C*').unpack1('H*')
"#{hex[0, 8]}-#{hex[8, 4]}-#{hex[12, 4]}-#{hex[16, 4]}-#{hex[20, 12]}"
end
Comment thread
marandaneto marked this conversation as resolved.

def uuid_v7_bytes
timestamp_ms = (Time.now.to_f * 1000).to_i & 0xffffffffffff
bytes = [
(timestamp_ms >> 40) & 0xff,
(timestamp_ms >> 32) & 0xff,
(timestamp_ms >> 24) & 0xff,
(timestamp_ms >> 16) & 0xff,
(timestamp_ms >> 8) & 0xff,
timestamp_ms & 0xff,
*SecureRandom.bytes(10).bytes
]

bytes[6] = (bytes[6] & 0x0f) | 0x70
bytes[8] = (bytes[8] & 0x3f) | 0x80
bytes
end
private_class_method :uuid_v7_bytes
end
end
16 changes: 8 additions & 8 deletions spec/posthog/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

module PostHog
flags_endpoint = 'https://us.i.posthog.com/flags/?v=2'
UUID_V7_REGEX =
/\A[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i

RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length = nil

Expand Down Expand Up @@ -259,8 +261,7 @@ module PostHog
client.capture(event: 'Event')

message = client.dequeue_last_message
expect(message[:distinct_id]).to be_a(String)
expect(message[:distinct_id].length).to eq(36)
expect(message[:distinct_id]).to match(UUID_V7_REGEX)
expect(message[:properties]['$process_person_profile']).to be false
end

Expand Down Expand Up @@ -771,7 +772,7 @@ module PostHog
)

message = client.dequeue_last_message
expect(message['uuid']).to match(/^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/i)
expect(message['uuid']).to match(UUID_V7_REGEX)
expect(logger).to have_received(:warn).with(
'UUID is not valid: i am also not a uuid. Ignoring it.'
)
Expand All @@ -788,7 +789,7 @@ module PostHog
)

message = client.dequeue_last_message
expect(message['uuid']).to match(/^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/i)
expect(message['uuid']).to match(UUID_V7_REGEX)
expect(logger).not_to have_received(:warn)
end

Expand All @@ -802,7 +803,7 @@ module PostHog
)

message = client.dequeue_last_message
expect(message['uuid']).to match(/^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/i)
expect(message['uuid']).to match(UUID_V7_REGEX)
expect(logger).to have_received(:warn).with(
'UUID is not valid: i am obviously not a uuid. Ignoring it.'
)
Expand Down Expand Up @@ -1068,7 +1069,7 @@ module PostHog
)

message = client.dequeue_last_message
expect(message['uuid']).to match(/^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/i)
expect(message['uuid']).to match(UUID_V7_REGEX)
expect(logger).to have_received(:warn).with(
'UUID is not a string. Ignoring it.'
)
Expand Down Expand Up @@ -1793,8 +1794,7 @@ def run

message = client.dequeue_last_message

expect(message[:distinct_id]).to be_a(String)
expect(message[:distinct_id].length).to eq(36)
expect(message[:distinct_id]).to match(UUID_V7_REGEX)
expect(message[:properties]['$process_person_profile']).to be false
end
end
Expand Down