diff --git a/core/modules/dblog/src/Controller/DbLogController.php b/core/modules/dblog/src/Controller/DbLogController.php index b52ed5015a2dddf44a0241ba566ddc3690b03da3..5a6ec7051b935b6b5eecba67ed64139c4c4f4ffe 100644 --- a/core/modules/dblog/src/Controller/DbLogController.php +++ b/core/modules/dblog/src/Controller/DbLogController.php @@ -291,6 +291,12 @@ public function eventDetails($event_id) { ['data' => ['#markup' => $dblog->link]], ], ]; + if (isset($dblog->backtrace)) { + $rows[] = [ + ['data' => $this->t('Backtrace'), 'header' => TRUE], + $dblog->backtrace, + ]; + } $build['dblog_table'] = [ '#type' => 'table', '#rows' => $rows, @@ -350,6 +356,10 @@ protected function buildFilterQuery(Request $request) { * The record from the watchdog table. The object properties are: wid, uid, * severity, type, timestamp, message, variables, link, name. * + * If the variables contain a @backtrace_string placeholder which is not + * used in the message, the formatted backtrace will be assigned to a new + * backtrace property on the row object which can be displayed separately. + * * @return string|\Drupal\Core\StringTranslation\TranslatableMarkup|false * The formatted log message or FALSE if the message or variables properties * are not set. @@ -372,6 +382,10 @@ public function formatMessage($row) { $variables['@backtrace_string'] = new FormattableMarkup( '<pre class="backtrace">@backtrace_string</pre>', $variables ); + // Save a reference so the backtrace can be displayed separately. + if (!str_contains($row->message, '@backtrace_string')) { + $row->backtrace = $variables['@backtrace_string']; + } } $message = $this->t(Xss::filterAdmin($row->message), $variables); } diff --git a/core/modules/dblog/tests/src/Functional/DbLogTest.php b/core/modules/dblog/tests/src/Functional/DbLogTest.php index b4ef303d04e504a62db5571572e4038e44438efa..f5d7e0467fe69a219766a6d5cba29bd36bccd6a4 100644 --- a/core/modules/dblog/tests/src/Functional/DbLogTest.php +++ b/core/modules/dblog/tests/src/Functional/DbLogTest.php @@ -142,6 +142,36 @@ public function testLogEventPage() { $this->assertSession()->pageTextContains('Notice'); } + /** + * Tests that the details page displays the backtrace for a logged \Throwable. + */ + public function testOnError(): void { + // Log in as the admin user. + $this->drupalLogin($this->adminUser); + + // Load a page that throws an exception in the controller, and includes its + // function arguments in the exception backtrace. + $this->drupalGet('error-test/trigger-exception'); + + // Load the details page for the most recent event logged by the "php" + // logger. + $query = Database::getConnection()->select('watchdog') + ->condition('type', 'php'); + $query->addExpression('MAX([wid])'); + $wid = $query->execute()->fetchField(); + $this->drupalGet('admin/reports/dblog/event/' . $wid); + + // Verify the page displays a dblog-event table with a "Type" header. + $table = $this->assertSession()->elementExists('xpath', "//table[@class='dblog-event']"); + $type = "//tr/th[contains(text(), 'Type')]/../td"; + $this->assertSession()->elementsCount('xpath', $type, 1, $table); + + // Verify that the backtrace row exists and is HTML-encoded. + $backtrace = "//tr//pre[contains(@class, 'backtrace')]"; + $this->assertCount(1, $table->findAll('xpath', $backtrace)); + $this->assertSession()->responseContains('<script>alert('xss')</script>'); + } + /** * Tests that a 403 event is logged with the exception triggering it. */ diff --git a/core/modules/system/tests/modules/error_test/src/Controller/ErrorTestController.php b/core/modules/system/tests/modules/error_test/src/Controller/ErrorTestController.php index b15bb9b00c664d34e7f0166a162db67ddc5b7cca..d2845e6d5ac0a12444c87a08779837115b8c5313 100644 --- a/core/modules/system/tests/modules/error_test/src/Controller/ErrorTestController.php +++ b/core/modules/system/tests/modules/error_test/src/Controller/ErrorTestController.php @@ -68,9 +68,17 @@ public function generateFatals() { /** * Trigger an exception to test the exception handler. + * + * @param string $argument + * A function argument which will be included in the exception backtrace. + * + * @throws \Exception */ - public function triggerException() { + public function triggerException(string $argument = "<script>alert('xss')</script>"): void { define('SIMPLETEST_COLLECT_ERRORS', FALSE); + // Add function arguments to the exception backtrace. + ini_set('zend.exception_ignore_args', FALSE); + ini_set('zend.exception_string_param_max_len', 1024); throw new \Exception("Drupal & awesome"); }