Skip to content

Commit ff1bf0e

Browse files
committed
Add structured trace for all DB drivers
1 parent 54cb511 commit ff1bf0e

File tree

10 files changed

+127
-21
lines changed

10 files changed

+127
-21
lines changed

system/Common.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -984,7 +984,7 @@ function render_backtrace(array $backtrace): string
984984
$frame['class'],
985985
$frame['type'],
986986
$frame['function'],
987-
$args
987+
$args,
988988
);
989989
}
990990

system/Database/MySQLi/Connection.php

Lines changed: 6 additions & 1 deletion
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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,14 @@ protected function execute(string $sql)
231231

232232
return $result;
233233
} catch (ErrorException $e) {
234-
log_message('error', (string) $e);
234+
$trace = array_slice($e->getTrace(), 2); // remove call to error handler
235+
236+
log_message('error', "{message}\nin {exFile} on line {exLine}.\n{trace}", [
237+
'message' => $e->getMessage(),
238+
'exFile' => clean_path($e->getFile()),
239+
'exLine' => $e->getLine(),
240+
'trace' => render_backtrace($trace),
241+
]);
235242

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

system/Database/Postgre/Connection.php

Lines changed: 8 additions & 1 deletion
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

Lines changed: 18 additions & 8 deletions
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);
@@ -492,17 +496,23 @@ public function setDatabase(?string $databaseName = null)
492496
*/
493497
protected function execute(string $sql)
494498
{
495-
$stmt = ($this->scrollable === false || $this->isWriteType($sql)) ?
496-
sqlsrv_query($this->connID, $sql) :
497-
sqlsrv_query($this->connID, $sql, [], ['Scrollable' => $this->scrollable]);
499+
$stmt = ($this->scrollable === false || $this->isWriteType($sql))
500+
? sqlsrv_query($this->connID, $sql)
501+
: sqlsrv_query($this->connID, $sql, [], ['Scrollable' => $this->scrollable]);
498502

499503
if ($stmt === false) {
500-
$error = $this->error();
504+
$trace = debug_backtrace();
505+
$first = array_shift($trace);
501506

502-
log_message('error', $error['message']);
507+
log_message('error', "{message}\nin {exFile} on line {exLine}.\n{trace}", [
508+
'message' => $this->getAllErrorMessages(),
509+
'exFile' => clean_path($first['file']),
510+
'exLine' => $first['line'],
511+
'trace' => render_backtrace($trace),
512+
]);
503513

504514
if ($this->DBDebug) {
505-
throw new DatabaseException($error['message']);
515+
throw new DatabaseException($this->getAllErrorMessages());
506516
}
507517
}
508518

system/Database/SQLite3/Connection.php

Lines changed: 6 additions & 1 deletion
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);

system/Debug/Exceptions.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ private function handleDeprecationError(string $message, ?string $file = null, ?
528528
'errFile' => clean_path($file ?? ''),
529529
'errLine' => $line ?? 0,
530530
'trace' => render_backtrace($trace),
531-
]
531+
],
532532
);
533533

534534
return true;
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of CodeIgniter 4 framework.
7+
*
8+
* (c) CodeIgniter Foundation <admin@codeigniter.com>
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\TestLogger;
18+
use Config\Database;
19+
use PHPUnit\Framework\Attributes\Group;
20+
21+
/**
22+
* @internal
23+
*/
24+
#[Group('DatabaseLive')]
25+
final class ExecuteLogMessageFormatTest extends CIUnitTestCase
26+
{
27+
protected function setUp(): void
28+
{
29+
parent::setUp();
30+
31+
self::setPrivateProperty(TestLogger::class, 'op_logs', []);
32+
}
33+
34+
protected function tearDown(): void
35+
{
36+
parent::tearDown();
37+
38+
self::setPrivateProperty(TestLogger::class, 'op_logs', []);
39+
40+
// reset the instances array as it now holds a modified connection under 'tests' group
41+
// @todo remove once 4.7 is updated with latest develop
42+
self::setPrivateProperty(Database::class, 'instances', []);
43+
}
44+
45+
public function testLogMessageWhenExecuteFailsShowFullStructuredBacktrace(): void
46+
{
47+
$db = Database::connect('tests', false);
48+
self::setPrivateProperty($db, 'DBDebug', false);
49+
50+
$sql = 'SELECT * FROM some_table WHERE id = ? AND status = ? AND author = ?';
51+
$db->query($sql, [3, 'live', 'Rick']);
52+
53+
$pattern = match ($db->DBDriver) {
54+
'MySQLi' => '/Table \'test\.some_table\' doesn\'t exist/',
55+
'Postgre' => '/pg_query\(\): Query failed: ERROR: relation "some_table" does not exist/',
56+
'SQLite3' => '/Unable to prepare statement:(\d+, )?no such table: some_table/',
57+
'OCI8' => '/oci_execute\(\): ORA-00942: table or view does not exist/',
58+
'SQLSRV' => '/\[Microsoft\]\[ODBC Driver \d+ for SQL Server\]\[SQL Server\]Invalid object name \'some_table\'/',
59+
default => '/Unknown DB error/',
60+
};
61+
$messageFromLogs = explode("\n", self::getPrivateProperty(TestLogger::class, 'op_logs')[0]['message']);
62+
63+
$this->assertMatchesRegularExpression($pattern, array_shift($messageFromLogs));
64+
65+
if ($db->DBDriver === 'Postgre') {
66+
$messageFromLogs = array_slice($messageFromLogs, 2);
67+
} elseif ($db->DBDriver === 'OCI8') {
68+
$messageFromLogs = array_slice($messageFromLogs, 1);
69+
}
70+
71+
$this->assertMatchesRegularExpression('/^in \S+ on line \d+\.$/', array_shift($messageFromLogs));
72+
73+
foreach ($messageFromLogs as $line) {
74+
$this->assertMatchesRegularExpression('/^\s*\d* .+(?:\(\d+\))?: \S+(?:(?:\->|::)\S+)?\(.*\)$/', $line);
75+
}
76+
}
77+
}

utils/phpstan-baseline/loader.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# total 2817 errors
1+
# total 2816 errors
22
includes:
33
- argument.type.neon
44
- assign.propertyType.neon

utils/phpstan-baseline/missingType.iterableValue.neon

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# total 1408 errors
1+
# total 1407 errors
22

33
parameters:
44
ignoreErrors:
@@ -2047,11 +2047,6 @@ parameters:
20472047
count: 1
20482048
path: ../../system/Debug/Exceptions.php
20492049

2050-
-
2051-
message: '#^Method CodeIgniter\\Debug\\Exceptions\:\:renderBacktrace\(\) has parameter \$backtrace with no value type specified in iterable type array\.$#'
2052-
count: 1
2053-
path: ../../system/Debug/Exceptions.php
2054-
20552050
-
20562051
message: '#^Property CodeIgniter\\Debug\\Iterator\:\:\$results type has no value type specified in iterable type array\.$#'
20572052
count: 1

0 commit comments

Comments
 (0)