From f012bbde1ffc7fc83bd063817e0426960612f591 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 12 Jul 2026 12:04:53 +0545 Subject: [PATCH 1/2] Add sdk and sdkVersion columns to audit logs --- CHANGELOG.md | 23 +++++++++++++++++++++ src/Audit/Adapter/ClickHouse.php | 28 ++++++++++++++++++++++++++ src/Audit/Log.php | 22 ++++++++++++++++++++ tests/Audit/Adapter/ClickHouseTest.php | 7 +++++-- 4 files changed, 78 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6fa6a2..9b9b4d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ All notable changes to `utopia-php/audit` are documented in this file. +## 2.7.0 + +### ClickHouse adapter — SDK columns + +The ClickHouse adapter now stores two additional optional columns capturing the +SDK that produced an audit event: + +#### Added + +- `Log::getSdk()` and `Log::getSdkVersion()` getters for ClickHouse-backed log reads. + +#### ClickHouse schema changes + +- Column `sdk` `LowCardinality(Nullable(String))` — SDK name (e.g. `web`, `flutter`, + `console`, `cli`); low-cardinality, optional. +- Column `sdkVersion` `Nullable(String)` — SDK version (e.g. `14.0.0`); high-cardinality, + optional. +- Index `_key_sdk` — bloom-filter index on the `sdk` column. + +Both columns are optional (`required = false`) so `createBatch()` never throws when a +caller omits them. Existing ClickHouse audit tables gain the columns via `setup()` or an +`ALTER TABLE ... ADD COLUMN IF NOT EXISTS` migration. + ## 2.4.0 ### ClickHouse adapter — actor terminology diff --git a/src/Audit/Adapter/ClickHouse.php b/src/Audit/Adapter/ClickHouse.php index 1524a5d..6c92af0 100644 --- a/src/Audit/Adapter/ClickHouse.php +++ b/src/Audit/Adapter/ClickHouse.php @@ -31,6 +31,7 @@ class ClickHouse extends SQL 'actorType', 'resourceType', 'country', + 'sdk', ]; /** @@ -536,6 +537,26 @@ public function getAttributes(): array 'array' => false, 'filters' => [], ], + [ + '$id' => 'sdk', + 'type' => Database::VAR_STRING, + 'size' => 256, + 'required' => false, + 'default' => null, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'sdkVersion', + 'type' => Database::VAR_STRING, + 'size' => 255, + 'required' => false, + 'default' => null, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], ]; } @@ -609,6 +630,13 @@ public function getIndexes(): array 'lengths' => [], 'orders' => [], ], + [ + '$id' => '_key_sdk', + 'type' => Database::INDEX_KEY, + 'attributes' => ['sdk'], + 'lengths' => [], + 'orders' => [], + ], ]; } diff --git a/src/Audit/Log.php b/src/Audit/Log.php index 3188ee7..f1a8636 100644 --- a/src/Audit/Log.php +++ b/src/Audit/Log.php @@ -101,6 +101,28 @@ public function getResource(): string return is_string($resource) ? $resource : ''; } + /** + * Get the SDK name associated with this log entry. + * + * @return string + */ + public function getSdk(): string + { + $sdk = $this->getAttribute('sdk', ''); + return is_string($sdk) ? $sdk : ''; + } + + /** + * Get the SDK version associated with this log entry. + * + * @return string + */ + public function getSdkVersion(): string + { + $sdkVersion = $this->getAttribute('sdkVersion', ''); + return is_string($sdkVersion) ? $sdkVersion : ''; + } + /** * Get the user agent string. * diff --git a/tests/Audit/Adapter/ClickHouseTest.php b/tests/Audit/Adapter/ClickHouseTest.php index c3c6a40..5b56173 100644 --- a/tests/Audit/Adapter/ClickHouseTest.php +++ b/tests/Audit/Adapter/ClickHouseTest.php @@ -535,7 +535,9 @@ public function testClickHouseAdapterAttributes(): void 'projectInternalId', 'teamId', 'teamInternalId', - 'hostname' + 'hostname', + 'sdk', + 'sdkVersion' ]; foreach ($expectedAttributes as $expected) { @@ -565,7 +567,8 @@ public function testClickHouseAdapterIndexes(): void '_key_actor_internal_id', '_key_actor_type', '_key_country', - '_key_hostname' + '_key_hostname', + '_key_sdk' ]; foreach ($expectedClickHouseIndexes as $expected) { From f8178a1cbf02e02da021ee07c2b0d46d2a019a3f Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 13 Jul 2026 07:16:32 +0545 Subject: [PATCH 2/2] Address review: nullable sdk getters, LENGTH_KEY sizes, accurate setup() changelog --- CHANGELOG.md | 6 ++++-- src/Audit/Adapter/ClickHouse.php | 10 ++++++---- src/Audit/Log.php | 21 +++++++++++++-------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b9b4d2..1507af3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,8 +22,10 @@ SDK that produced an audit event: - Index `_key_sdk` — bloom-filter index on the `sdk` column. Both columns are optional (`required = false`) so `createBatch()` never throws when a -caller omits them. Existing ClickHouse audit tables gain the columns via `setup()` or an -`ALTER TABLE ... ADD COLUMN IF NOT EXISTS` migration. +caller omits them. Newly created tables include the columns automatically via `setup()`. +`setup()` only issues `CREATE TABLE IF NOT EXISTS`, so **existing** tables do not gain the +columns automatically — apply them with an `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` +migration. ## 2.4.0 diff --git a/src/Audit/Adapter/ClickHouse.php b/src/Audit/Adapter/ClickHouse.php index 6c92af0..3d9134a 100644 --- a/src/Audit/Adapter/ClickHouse.php +++ b/src/Audit/Adapter/ClickHouse.php @@ -540,20 +540,22 @@ public function getAttributes(): array [ '$id' => 'sdk', 'type' => Database::VAR_STRING, - 'size' => 256, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, 'required' => false, 'default' => null, - 'signed' => true, 'array' => false, 'filters' => [], ], [ '$id' => 'sdkVersion', 'type' => Database::VAR_STRING, - 'size' => 255, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, 'required' => false, 'default' => null, - 'signed' => true, 'array' => false, 'filters' => [], ], diff --git a/src/Audit/Log.php b/src/Audit/Log.php index f1a8636..74febc9 100644 --- a/src/Audit/Log.php +++ b/src/Audit/Log.php @@ -104,23 +104,28 @@ public function getResource(): string /** * Get the SDK name associated with this log entry. * - * @return string + * Optional column: returns null when the SDK was never recorded, mirroring + * the other nullable actor columns (getActorId/getActorInternalId). + * + * @return string|null */ - public function getSdk(): string + public function getSdk(): ?string { - $sdk = $this->getAttribute('sdk', ''); - return is_string($sdk) ? $sdk : ''; + $sdk = $this->getAttribute('sdk'); + return is_string($sdk) ? $sdk : null; } /** * Get the SDK version associated with this log entry. * - * @return string + * Optional column: returns null when the SDK version was never recorded. + * + * @return string|null */ - public function getSdkVersion(): string + public function getSdkVersion(): ?string { - $sdkVersion = $this->getAttribute('sdkVersion', ''); - return is_string($sdkVersion) ? $sdkVersion : ''; + $sdkVersion = $this->getAttribute('sdkVersion'); + return is_string($sdkVersion) ? $sdkVersion : null; } /**