Skip to content

Commit ac9aa6e

Browse files
committed
feat: add query logging functionality to ClickHouse adapter for improved debugging
1 parent 6fc9cb8 commit ac9aa6e

1 file changed

Lines changed: 93 additions & 3 deletions

File tree

src/Usage/Adapter/ClickHouse.php

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ class ClickHouse extends SQL
6363

6464
protected string $namespace = '';
6565

66+
/** @var bool Whether to log queries for debugging */
67+
private bool $enableQueryLogging = false;
68+
69+
/** @var array<array{sql: string, params: array<string, mixed>, duration: float, timestamp: float, success: bool, error?: string}> Query execution log */
70+
private array $queryLog = [];
71+
6672
/**
6773
* @param string $host ClickHouse host
6874
* @param string $username ClickHouse username (default: 'default')
@@ -102,6 +108,39 @@ public function setUseFinal(bool $useFinal): self
102108
return $this;
103109
}
104110

111+
/**
112+
* Enable or disable query logging for debugging.
113+
*
114+
* @param bool $enable Whether to enable query logging
115+
* @return self
116+
*/
117+
public function enableQueryLogging(bool $enable = true): self
118+
{
119+
$this->enableQueryLogging = $enable;
120+
return $this;
121+
}
122+
123+
/**
124+
* Get the query execution log.
125+
*
126+
* @return array<array{sql: string, params: array<string, mixed>, duration: float, timestamp: float, success: bool, error?: string}>
127+
*/
128+
public function getQueryLog(): array
129+
{
130+
return $this->queryLog;
131+
}
132+
133+
/**
134+
* Clear the query execution log.
135+
*
136+
* @return self
137+
*/
138+
public function clearQueryLog(): self
139+
{
140+
$this->queryLog = [];
141+
return $this;
142+
}
143+
105144
/**
106145
* Get adapter name.
107146
*/
@@ -310,6 +349,36 @@ private function getCounterTableName(): string
310349
return $tableName;
311350
}
312351

352+
/**
353+
* Log a query execution for debugging purposes.
354+
*
355+
* @param string $sql SQL query executed
356+
* @param array<string, mixed> $params Query parameters
357+
* @param float $duration Execution duration in seconds
358+
* @param bool $success Whether the query succeeded
359+
* @param string|null $error Error message if query failed
360+
*/
361+
private function logQuery(string $sql, array $params, float $duration, bool $success, ?string $error = null): void
362+
{
363+
if (!$this->enableQueryLogging) {
364+
return;
365+
}
366+
367+
$logEntry = [
368+
'sql' => $sql,
369+
'params' => $params,
370+
'duration' => $duration,
371+
'timestamp' => microtime(true),
372+
'success' => $success,
373+
];
374+
375+
if ($error !== null) {
376+
$logEntry['error'] = $error;
377+
}
378+
379+
$this->queryLog[] = $logEntry;
380+
}
381+
313382
/**
314383
* Execute a ClickHouse query via HTTP interface using Fetch Client.
315384
*
@@ -331,6 +400,7 @@ private function getCounterTableName(): string
331400
*/
332401
private function query(string $sql, array $params = []): string
333402
{
403+
$startTime = microtime(true);
334404
$scheme = $this->secure ? 'https' : 'http';
335405
$url = "{$scheme}://{$this->host}:{$this->port}/";
336406

@@ -353,12 +423,20 @@ private function query(string $sql, array $params = []): string
353423
if ($response->getStatusCode() !== 200) {
354424
$bodyStr = $response->getBody();
355425
$bodyStr = is_string($bodyStr) ? $bodyStr : '';
356-
throw new Exception("ClickHouse query failed with HTTP {$response->getStatusCode()}: {$bodyStr}");
426+
$duration = microtime(true) - $startTime;
427+
$errorMsg = "ClickHouse query failed with HTTP {$response->getStatusCode()}: {$bodyStr}";
428+
$this->logQuery($sql, $params, $duration, false, $errorMsg);
429+
throw new Exception($errorMsg);
357430
}
358431

359432
$body = $response->getBody();
360-
return is_string($body) ? $body : '';
433+
$result = is_string($body) ? $body : '';
434+
$duration = microtime(true) - $startTime;
435+
$this->logQuery($sql, $params, $duration, true);
436+
return $result;
361437
} catch (Exception $e) {
438+
$duration = microtime(true) - $startTime;
439+
$this->logQuery($sql, $params, $duration, false, $e->getMessage());
362440
// Preserve the original exception context for better debugging
363441
// Re-throw with additional context while maintaining the original exception chain
364442
throw new Exception(
@@ -384,6 +462,7 @@ private function insert(string $table, array $data): void
384462
return;
385463
}
386464

465+
$startTime = microtime(true);
387466
$scheme = $this->secure ? 'https' : 'http';
388467
$escapedTable = $this->escapeIdentifier($table);
389468
$url = "{$scheme}://{$this->host}:{$this->port}/?query=INSERT+INTO+{$escapedTable}+FORMAT+JSONEachRow";
@@ -395,6 +474,9 @@ private function insert(string $table, array $data): void
395474
// Join JSON strings with newlines
396475
$body = implode("\n", $data);
397476

477+
$sql = "INSERT INTO {$escapedTable} FORMAT JSONEachRow";
478+
$params = ['rows' => count($data), 'bytes' => strlen($body)];
479+
398480
try {
399481
$response = $this->client->fetch(
400482
url: $url,
@@ -405,9 +487,17 @@ private function insert(string $table, array $data): void
405487
if ($response->getStatusCode() !== 200) {
406488
$bodyStr = $response->getBody();
407489
$bodyStr = is_string($bodyStr) ? $bodyStr : '';
408-
throw new Exception("ClickHouse insert failed with HTTP {$response->getStatusCode()}: {$bodyStr}");
490+
$duration = microtime(true) - $startTime;
491+
$errorMsg = "ClickHouse insert failed with HTTP {$response->getStatusCode()}: {$bodyStr}";
492+
$this->logQuery($sql, $params, $duration, false, $errorMsg);
493+
throw new Exception($errorMsg);
409494
}
495+
496+
$duration = microtime(true) - $startTime;
497+
$this->logQuery($sql, $params, $duration, true);
410498
} catch (Exception $e) {
499+
$duration = microtime(true) - $startTime;
500+
$this->logQuery($sql, $params, $duration, false, $e->getMessage());
411501
throw new Exception(
412502
"ClickHouse insert execution failed: {$e->getMessage()}",
413503
0,

0 commit comments

Comments
 (0)