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
7 changes: 7 additions & 0 deletions .changeset/uuid-v7-personless-ids.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"posthog": patch
"posthog-android": patch
"posthog-server": patch
---

Generate UUID v7 values for SDK-created personless distinct IDs.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.posthog.server

import java.util.UUID
import com.posthog.vendor.uuid.TimeBasedEpochGenerator

/**
* Request-scoped PostHog analytics context.
Expand Down Expand Up @@ -181,7 +181,7 @@ public class PostHogRequestContext private constructor() {
val resolvedDistinctId = resolveDistinctId(distinctId)
val isPersonless = resolvedDistinctId.isNullOrBlank()
val mergedProperties = properties?.toMutableMap() ?: mutableMapOf()
val captureDistinctId = resolvedDistinctId ?: UUID.randomUUID().toString()
val captureDistinctId = resolvedDistinctId ?: TimeBasedEpochGenerator.generate().toString()

if (isPersonless) {
mergedProperties.putIfAbsent(PROCESS_PERSON_PROFILE_PROPERTY, false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ internal class PostHogRequestContextTest {
val distinctId = batch.firstEvent?.get("distinct_id")?.asString
assertNotNull(distinctId)
assertNotEquals("header-user", distinctId)
assertTrue(runCatching { java.util.UUID.fromString(distinctId) }.isSuccess)
val parsedDistinctId = java.util.UUID.fromString(distinctId)
assertEquals(7, parsedDistinctId.version())
val properties = batch.firstEventProperties()
assertEquals(false, properties["\$process_person_profile"])
assertFalse(properties.containsKey("\$session_id"))
Expand All @@ -176,7 +177,8 @@ internal class PostHogRequestContextTest {
val batch = request.parseBatch()
val distinctId = batch.firstEvent?.get("distinct_id")?.asString
assertNotNull(distinctId)
assertTrue(runCatching { java.util.UUID.fromString(distinctId) }.isSuccess)
val parsedDistinctId = java.util.UUID.fromString(distinctId)
assertEquals(7, parsedDistinctId.version())
val properties = batch.firstEventProperties()
assertEquals(false, properties["\$process_person_profile"])
assertEquals("present", properties["request"])
Expand Down
2 changes: 1 addition & 1 deletion posthog/src/main/java/com/posthog/PostHogEvent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import java.util.UUID
* @property distinctId The distinct Id
* @property properties All the event properties
* @property timestamp The timestamp is automatically generated
* @property uuid the UUID v4 is automatically generated and used for deduplication
* @property uuid the UUID v7 is automatically generated and used for deduplication
*/
public data class PostHogEvent(
val event: String,
Expand Down
4 changes: 2 additions & 2 deletions posthog/src/main/java/com/posthog/PostHogStateless.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import com.posthog.internal.PostHogPrintLogger
import com.posthog.internal.PostHogQueueInterface
import com.posthog.internal.PostHogThreadFactory
import com.posthog.internal.errortracking.ThrowableCoercer
import com.posthog.vendor.uuid.TimeBasedEpochGenerator
import java.util.Date
import java.util.UUID
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors

Expand Down Expand Up @@ -599,7 +599,7 @@ public open class PostHogStateless protected constructor(
var id = distinctId
if (id.isNullOrBlank()) {
exceptionProperties.set("\$process_person_profile", false)
id = UUID.randomUUID().toString()
id = TimeBasedEpochGenerator.generate().toString()
}

captureStateless(PostHogEventName.EXCEPTION.event, distinctId = id, properties = exceptionProperties)
Expand Down
4 changes: 3 additions & 1 deletion posthog/src/test/java/com/posthog/PostHogStatelessTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import java.io.File
import java.util.Date
import java.util.UUID
import java.util.concurrent.Executors
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
Expand Down Expand Up @@ -1349,7 +1350,7 @@ internal class PostHogStatelessTest {
}

@Test
fun `captureExceptionStateless without distinctId generates random UUID`() {
fun `captureExceptionStateless without distinctId generates UUID v7`() {
val mockQueue = MockQueue()
sut = createStatelessInstance()
config = createConfig()
Expand All @@ -1365,6 +1366,7 @@ internal class PostHogStatelessTest {
val event = mockQueue.events.first()
assertTrue(event.distinctId.isNotBlank())
assertTrue(event.distinctId.matches(("[0-9a-fA-F-]{36}".toRegex())))
assertEquals(7, UUID.fromString(event.distinctId).version())
assertEquals(false, event.properties!!["\$process_person_profile"])
}

Expand Down
Loading