Skip to content

Commit 196c605

Browse files
committed
Add structured trace for all DB drivers
1 parent 427eeeb commit 196c605

File tree

7 files changed

+129
-18
lines changed

7 files changed

+129
-18
lines changed

system/Database/MySQLi/Connection.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,12 @@ protected function execute(string $sql)
326326
try {
327327
return $this->connID->query($this->prepQuery($sql), $this->resultMode);
328328
} catch (mysqli_sql_exception $e) {
329-
log_message('error', (string) $e);
329+
log_message('error', "{message}\nin {exFile} on line {exLine}.\n{trace}", [
330+
'message' => $e->getMessage(),
331+
'exFile' => clean_path($e->getFile()),
332+
'exLine' => $e->getLine(),
333+
'trace' => render_backtrace($e->getTrace()),
334+
]);
330335

331336
if ($this->DBDebug) {
332337
throw new DatabaseException($e->getMessage(), $e->getCode(), $e);

system/Database/OCI8/Connection.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,14 @@ protected function execute(string $sql)
226226

227227
return $result;
228228
} catch (ErrorException $e) {
229-
log_message('error', (string) $e);
229+
$trace = array_slice($e->getTrace(), 2); // remove call to error handler
230+
231+
log_message('error', "{message}\nin {exFile} on line {exLine}.\n{trace}", [
232+
'message' => $e->getMessage(),
233+
'exFile' => clean_path($e->getFile()),
234+
'exLine' => $e->getLine(),
235+
'trace' => render_backtrace($trace),
236+
]);
230237

231238
if ($this->DBDebug) {
232239
throw new DatabaseException($e->getMessage(), $e->getCode(), $e);

system/Database/Postgre/Connection.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,14 @@ protected function execute(string $sql)
205205
try {
206206
return pg_query($this->connID, $sql);
207207
} catch (ErrorException $e) {
208-
log_message('error', (string) $e);
208+
$trace = array_slice($e->getTrace(), 2); // remove the call to error handler
209+
210+
log_message('error', "{message}\nin {exFile} on line {exLine}.\n{trace}", [
211+
'message' => $e->getMessage(),
212+
'exFile' => clean_path($e->getFile()),
213+
'exLine' => $e->getLine(),
214+
'trace' => render_backtrace($trace),
215+
]);
209216

210217
if ($this->DBDebug) {
211218
throw new DatabaseException($e->getMessage(), $e->getCode(), $e);

system/Database/SQLSRV/Connection.php

+18-8
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,12 @@ public function getAllErrorMessages(): string
155155
$errors = [];
156156

157157
foreach (sqlsrv_errors() as $error) {
158-
$errors[] = $error['message']
159-
. ' SQLSTATE: ' . $error['SQLSTATE'] . ', code: ' . $error['code'];
158+
$errors[] = sprintf(
159+
'%s SQLSTATE: %s, code: %s',
160+
$error['message'],
161+
$error['SQLSTATE'],
162+
$error['code']
163+
);
160164
}
161165

162166
return implode("\n", $errors);
@@ -488,17 +492,23 @@ public function setDatabase(?string $databaseName = null)
488492
*/
489493
protected function execute(string $sql)
490494
{
491-
$stmt = ($this->scrollable === false || $this->isWriteType($sql)) ?
492-
sqlsrv_query($this->connID, $sql) :
493-
sqlsrv_query($this->connID, $sql, [], ['Scrollable' => $this->scrollable]);
495+
$stmt = ($this->scrollable === false || $this->isWriteType($sql))
496+
? sqlsrv_query($this->connID, $sql)
497+
: sqlsrv_query($this->connID, $sql, [], ['Scrollable' => $this->scrollable]);
494498

495499
if ($stmt === false) {
496-
$error = $this->error();
500+
$trace = debug_backtrace();
501+
$first = array_shift($trace);
497502

498-
log_message('error', $error['message']);
503+
log_message('error', "{message}\nin {exFile} on line {exLine}.\n{trace}", [
504+
'message' => $this->getAllErrorMessages(),
505+
'exFile' => clean_path($first['file']),
506+
'exLine' => $first['line'],
507+
'trace' => render_backtrace($trace),
508+
]);
499509

500510
if ($this->DBDebug) {
501-
throw new DatabaseException($error['message']);
511+
throw new DatabaseException($this->getAllErrorMessages());
502512
}
503513
}
504514

system/Database/SQLite3/Connection.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,12 @@ protected function execute(string $sql)
175175
? $this->connID->exec($sql)
176176
: $this->connID->query($sql);
177177
} catch (Exception $e) {
178-
log_message('error', (string) $e);
178+
log_message('error', "{message}\nin {exFile} on line {exLine}.\n{trace}", [
179+
'message' => $e->getMessage(),
180+
'exFile' => clean_path($e->getFile()),
181+
'exLine' => $e->getLine(),
182+
'trace' => render_backtrace($e->getTrace()),
183+
]);
179184

180185
if ($this->DBDebug) {
181186
throw new DatabaseException($e->getMessage(), $e->getCode(), $e);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of CodeIgniter 4 framework.
7+
*
8+
* (c) CodeIgniter Foundation <[email protected]>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace CodeIgniter\Database\Live;
15+
16+
use CodeIgniter\Test\CIUnitTestCase;
17+
use CodeIgniter\Test\DatabaseTestTrait;
18+
use CodeIgniter\Test\TestLogger;
19+
use Config\Database;
20+
use PHPUnit\Framework\Attributes\Group;
21+
22+
/**
23+
* @internal
24+
*/
25+
#[Group('DatabaseLive')]
26+
final class ExecuteLogMessageFormatTest extends CIUnitTestCase
27+
{
28+
use DatabaseTestTrait;
29+
30+
protected function setUp(): void
31+
{
32+
parent::setUp();
33+
34+
$utils = Database::utils();
35+
$forge = Database::forge();
36+
37+
foreach (['foobar', 'test_com.sitedb.web'] as $database) {
38+
if ($utils->databaseExists($database)) {
39+
$forge->dropDatabase($database);
40+
}
41+
}
42+
43+
$this->disableDBDebug();
44+
}
45+
46+
protected function tearDown(): void
47+
{
48+
$this->enableDBDebug();
49+
50+
parent::tearDown();
51+
}
52+
53+
public function testLogMessageWhenExecuteFailsShowFullStructuredBacktrace(): void
54+
{
55+
$sql = 'SELECT * FROM some_table WHERE id = ? AND status = ? AND author = ?';
56+
$this->db->query($sql, [3, 'live', 'Rick']);
57+
58+
$message = match ($this->db->DBDriver) {
59+
'MySQLi' => 'Table \'test.some_table\' doesn\'t exist',
60+
'Postgre' => 'pg_query(): Query failed: ERROR: relation "some_table" does not exist',
61+
'SQLite3' => 'Unable to prepare statement: no such table: some_table',
62+
'OCI8' => 'oci_execute(): ORA-00942: table or view does not exist',
63+
'SQLSRV' => '[Microsoft][ODBC Driver 18 for SQL Server][SQL Server]Invalid object name \'some_table\'. SQLSTATE: 42S02, code: 208',
64+
default => 'Unknown DB error',
65+
};
66+
$messageFromLogs = explode("\n", $this->getPrivateProperty(TestLogger::class, 'op_logs')[0]['message']);
67+
68+
$this->assertSame($message, array_shift($messageFromLogs));
69+
70+
if ($this->db->DBDriver === 'Postgre') {
71+
$messageFromLogs = array_slice($messageFromLogs, 2);
72+
} elseif ($this->db->DBDriver === 'OCI8') {
73+
$messageFromLogs = array_slice($messageFromLogs, 1);
74+
}
75+
76+
$this->assertMatchesRegularExpression('/^in \S+ on line \d+\.$/', array_shift($messageFromLogs));
77+
78+
foreach ($messageFromLogs as $line) {
79+
$this->assertMatchesRegularExpression('/^\s*\d* .+(?:\(\d+\))?: \S+(?:(?:\->|::)\S+)?\(.*\)$/', $line);
80+
}
81+
}
82+
}

utils/phpstan-baseline/missingType.iterableValue.neon

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# total 1672 errors
1+
# total 1670 errors
22

33
parameters:
44
ignoreErrors:
@@ -2417,11 +2417,6 @@ parameters:
24172417
count: 1
24182418
path: ../../system/Debug/Exceptions.php
24192419

2420-
-
2421-
message: '#^Method CodeIgniter\\Debug\\Exceptions\:\:renderBacktrace\(\) has parameter \$backtrace with no value type specified in iterable type array\.$#'
2422-
count: 1
2423-
path: ../../system/Debug/Exceptions.php
2424-
24252420
-
24262421
message: '#^Method CodeIgniter\\Debug\\Exceptions\:\:respond\(\) has parameter \$data with no value type specified in iterable type array\.$#'
24272422
count: 1

0 commit comments

Comments
 (0)