diff --git a/system/Database/MySQLi/Connection.php b/system/Database/MySQLi/Connection.php index 60de6d8dc24e..f3449d4a0c65 100644 --- a/system/Database/MySQLi/Connection.php +++ b/system/Database/MySQLi/Connection.php @@ -326,7 +326,12 @@ protected function execute(string $sql) try { return $this->connID->query($this->prepQuery($sql), $this->resultMode); } catch (mysqli_sql_exception $e) { - log_message('error', (string) $e); + log_message('error', "{message}\nin {exFile} on line {exLine}.\n{trace}", [ + 'message' => $e->getMessage(), + 'exFile' => clean_path($e->getFile()), + 'exLine' => $e->getLine(), + 'trace' => render_backtrace($e->getTrace()), + ]); if ($this->DBDebug) { throw new DatabaseException($e->getMessage(), $e->getCode(), $e); diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 53a5365bc90d..00da4492d745 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -226,7 +226,14 @@ protected function execute(string $sql) return $result; } catch (ErrorException $e) { - log_message('error', (string) $e); + $trace = array_slice($e->getTrace(), 2); // remove call to error handler + + log_message('error', "{message}\nin {exFile} on line {exLine}.\n{trace}", [ + 'message' => $e->getMessage(), + 'exFile' => clean_path($e->getFile()), + 'exLine' => $e->getLine(), + 'trace' => render_backtrace($trace), + ]); if ($this->DBDebug) { throw new DatabaseException($e->getMessage(), $e->getCode(), $e); diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index e808930663dc..e3368ed77792 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -205,7 +205,14 @@ protected function execute(string $sql) try { return pg_query($this->connID, $sql); } catch (ErrorException $e) { - log_message('error', (string) $e); + $trace = array_slice($e->getTrace(), 2); // remove the call to error handler + + log_message('error', "{message}\nin {exFile} on line {exLine}.\n{trace}", [ + 'message' => $e->getMessage(), + 'exFile' => clean_path($e->getFile()), + 'exLine' => $e->getLine(), + 'trace' => render_backtrace($trace), + ]); if ($this->DBDebug) { throw new DatabaseException($e->getMessage(), $e->getCode(), $e); diff --git a/system/Database/SQLSRV/Connection.php b/system/Database/SQLSRV/Connection.php index 95f75cf44768..2a64741e0bfa 100644 --- a/system/Database/SQLSRV/Connection.php +++ b/system/Database/SQLSRV/Connection.php @@ -488,14 +488,21 @@ public function setDatabase(?string $databaseName = null) */ protected function execute(string $sql) { - $stmt = ($this->scrollable === false || $this->isWriteType($sql)) ? - sqlsrv_query($this->connID, $sql) : - sqlsrv_query($this->connID, $sql, [], ['Scrollable' => $this->scrollable]); + $stmt = ($this->scrollable === false || $this->isWriteType($sql)) + ? sqlsrv_query($this->connID, $sql) + : sqlsrv_query($this->connID, $sql, [], ['Scrollable' => $this->scrollable]); if ($stmt === false) { $error = $this->error(); - - log_message('error', $error['message']); + $trace = debug_backtrace(); + $first = array_shift($trace); + + log_message('error', "{message}\nin {exFile} on line {exLine}.\n{trace}", [ + 'message' => $error['message'], + 'exFile' => clean_path($first['file']), + 'exLine' => $first['line'], + 'trace' => render_backtrace($trace), + ]); if ($this->DBDebug) { throw new DatabaseException($error['message']); diff --git a/system/Database/SQLite3/Connection.php b/system/Database/SQLite3/Connection.php index 0d3290b38d9d..9a7dc53c6445 100644 --- a/system/Database/SQLite3/Connection.php +++ b/system/Database/SQLite3/Connection.php @@ -175,7 +175,12 @@ protected function execute(string $sql) ? $this->connID->exec($sql) : $this->connID->query($sql); } catch (Exception $e) { - log_message('error', (string) $e); + log_message('error', "{message}\nin {exFile} on line {exLine}.\n{trace}", [ + 'message' => $e->getMessage(), + 'exFile' => clean_path($e->getFile()), + 'exLine' => $e->getLine(), + 'trace' => render_backtrace($e->getTrace()), + ]); if ($this->DBDebug) { throw new DatabaseException($e->getMessage(), $e->getCode(), $e); diff --git a/tests/system/Database/Live/ExecuteLogMessageFormatTest.php b/tests/system/Database/Live/ExecuteLogMessageFormatTest.php new file mode 100644 index 000000000000..ab36472b0fd4 --- /dev/null +++ b/tests/system/Database/Live/ExecuteLogMessageFormatTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Database\Live; + +use CodeIgniter\Test\CIUnitTestCase; +use CodeIgniter\Test\DatabaseTestTrait; +use CodeIgniter\Test\TestLogger; +use Config\Database; +use PHPUnit\Framework\Attributes\Group; + +/** + * @internal + */ +#[Group('DatabaseLive')] +final class ExecuteLogMessageFormatTest extends CIUnitTestCase +{ + use DatabaseTestTrait; + + protected function setUp(): void + { + $this->db = Database::connect(getShared: false); + $this->disableDBDebug(); + + parent::setUp(); + } + + protected function tearDown(): void + { + $this->enableDBDebug(); + + parent::tearDown(); + } + + public function testLogMessageWhenExecuteFailsShowFullStructuredBacktrace(): void + { + $sql = 'SELECT * FROM some_table WHERE id = ? AND status = ? AND author = ?'; + $this->db->query($sql, [3, 'live', 'Rick']); + + $message = match ($this->db->DBDriver) { + 'MySQLi' => 'Table \'test.some_table\' doesn\'t exist', + 'Postgre' => 'pg_query(): Query failed: ERROR: relation "some_table" does not exist', + 'SQLite3' => 'Unable to prepare statement: no such table: some_table', + 'OCI8' => 'oci_execute(): ORA-00942: table or view does not exist', + 'SQLSRV' => '[Microsoft][ODBC Driver 18 for SQL Server][SQL Server]Invalid object name \'some_table\'.', + default => 'Unknown DB error', + }; + $messageFromLogs = explode("\n", $this->getPrivateProperty(TestLogger::class, 'op_logs')[0]['message']); + + $this->assertSame($message, array_shift($messageFromLogs)); + + if ($this->db->DBDriver === 'Postgre') { + $messageFromLogs = array_slice($messageFromLogs, 2); + } elseif ($this->db->DBDriver === 'OCI8') { + $messageFromLogs = array_slice($messageFromLogs, 1); + } + + $this->assertMatchesRegularExpression('/^in \S+ on line \d+\.$/', array_shift($messageFromLogs)); + + foreach ($messageFromLogs as $line) { + $this->assertMatchesRegularExpression('/^\s*\d* .+(?:\(\d+\))?: \S+(?:(?:\->|::)\S+)?\(.*\)$/', $line); + } + } +} diff --git a/utils/phpstan-baseline/missingType.iterableValue.neon b/utils/phpstan-baseline/missingType.iterableValue.neon index 4dac1bf252c4..ac9423e03f98 100644 --- a/utils/phpstan-baseline/missingType.iterableValue.neon +++ b/utils/phpstan-baseline/missingType.iterableValue.neon @@ -1,4 +1,4 @@ -# total 1672 errors +# total 1670 errors parameters: ignoreErrors: @@ -2417,11 +2417,6 @@ parameters: count: 1 path: ../../system/Debug/Exceptions.php - - - message: '#^Method CodeIgniter\\Debug\\Exceptions\:\:renderBacktrace\(\) has parameter \$backtrace with no value type specified in iterable type array\.$#' - count: 1 - path: ../../system/Debug/Exceptions.php - - message: '#^Method CodeIgniter\\Debug\\Exceptions\:\:respond\(\) has parameter \$data with no value type specified in iterable type array\.$#' count: 1