[HttpKernel][WebProfilerBundle] Add error indicator to profiler list view

This commit is contained in:
llupa
2026-02-06 13:50:35 +01:00
committed by Nicolas Grekas
parent d7948fbea2
commit f8b6946fff
5 changed files with 69 additions and 1 deletions

View File

@@ -4,6 +4,7 @@ CHANGELOG
8.1
---
* Add `hasErrors()` method to `Profile` to track profiles with errors (exceptions or error-level logs)
* Validate typed route parameters before calling controllers and return an HTTP error when an invalid value is provided
* Add `ControllerAttributeEvent` et al. to dispatch events named after controller attributes
* Add support for `UploadedFile` when using `MapRequestPayload`

View File

@@ -62,7 +62,7 @@ class FileProfilerStorage implements ProfilerStorageInterface
continue;
}
[$csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode, $csvVirtualType] = $values + [7 => null];
[$csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode, $csvVirtualType, $csvHasErrors] = $values + [7 => null, 8 => null];
$csvTime = (int) $csvTime;
$urlFilter = false;
@@ -91,6 +91,7 @@ class FileProfilerStorage implements ProfilerStorageInterface
'parent' => $csvParent,
'status_code' => $csvStatusCode,
'virtual_type' => $csvVirtualType ?: 'request',
'has_errors' => (bool) $csvHasErrors,
];
if ($filter && !$filter($profile)) {
@@ -159,6 +160,7 @@ class FileProfilerStorage implements ProfilerStorageInterface
'time' => $profile->getTime(),
'status_code' => $profile->getStatusCode(),
'virtual_type' => $profile->getVirtualType() ?? 'request',
'has_errors' => $profile->hasErrors(),
];
$data = serialize($data);
@@ -186,6 +188,7 @@ class FileProfilerStorage implements ProfilerStorageInterface
$profile->getParentToken(),
$profile->getStatusCode(),
$profile->getVirtualType() ?? 'request',
$profile->hasErrors() ? '1' : '0',
], ',', '"', '\\');
fclose($file);
@@ -271,6 +274,7 @@ class FileProfilerStorage implements ProfilerStorageInterface
$profile->setTime($data['time']);
$profile->setStatusCode($data['status_code']);
$profile->setVirtualType($data['virtual_type'] ?: 'request');
$profile->setHasErrors($data['has_errors'] ?? false);
$profile->setCollectors($data['data']);
if (!$parent && $data['parent']) {

View File

@@ -32,6 +32,7 @@ class Profile
private ?int $statusCode = null;
private ?self $parent = null;
private ?string $virtualType = null;
private bool $hasErrors = false;
/**
* @var Profile[]
@@ -155,6 +156,16 @@ class Profile
return $this->virtualType;
}
public function hasErrors(): bool
{
return $this->hasErrors;
}
public function setHasErrors(bool $hasErrors): void
{
$this->hasErrors = $hasErrors;
}
/**
* Finds children profilers.
*
@@ -261,6 +272,7 @@ class Profile
'time' => $this->time,
'statusCode' => $this->statusCode,
'virtualType' => $this->virtualType,
'hasErrors' => $this->hasErrors,
];
}
}

View File

@@ -17,6 +17,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
use Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector;
use Symfony\Contracts\Service\ResetInterface;
/**
@@ -94,6 +95,15 @@ class Profiler implements ResetInterface
}
}
// Update hasErrors flag to include error-level logs (available after lateCollect)
if (!$profile->hasErrors()
&& $profile->hasCollector('logger')
&& ($logger = $profile->getCollector('logger')) instanceof LoggerDataCollector
&& $logger->countErrors() > 0
) {
$profile->setHasErrors(true);
}
if (!($ret = $this->storage->write($profile)) && null !== $this->logger) {
$this->logger->warning('Unable to store the profiler information.', ['configured_storage' => $this->storage::class]);
}
@@ -148,6 +158,8 @@ class Profiler implements ResetInterface
$profile->setVirtualType($request->attributes->get('_virtual_type'));
}
$profile->setHasErrors(null !== $exception);
if ($prevToken = $response->headers->get('X-Debug-Token')) {
$response->headers->set('X-Previous-Debug-Token', $prevToken);
}

View File

@@ -329,6 +329,45 @@ class FileProfilerStorageTest extends TestCase
$this->assertContains((int) $tokens[1]['status_code'], [200, 404]);
}
public function testHasErrors()
{
$profile = new Profile('token_with_errors');
$profile->setIp('127.0.0.1');
$profile->setUrl('http://foo.bar/error');
$profile->setMethod('GET');
$profile->setStatusCode(500);
$profile->setHasErrors(true);
$this->storage->write($profile);
$profile = new Profile('token_without_errors');
$profile->setIp('127.0.0.1');
$profile->setUrl('http://foo.bar/success');
$profile->setMethod('GET');
$profile->setStatusCode(200);
$profile->setHasErrors(false);
$this->storage->write($profile);
$loadedProfile = $this->storage->read('token_with_errors');
$this->assertTrue($loadedProfile->hasErrors(), '->read() restores hasErrors=true on the Profile object');
$loadedProfile = $this->storage->read('token_without_errors');
$this->assertFalse($loadedProfile->hasErrors(), '->read() restores hasErrors=false on the Profile object');
}
public function testHasErrorsBackwardCompatibility()
{
// Test backward compatibility with old CSV lines that don't have has_errors field
$file = $this->tmpDir.'/index.csv';
$time = time();
// Write an old-format CSV line (8 fields, no has_errors)
file_put_contents($file, "old_token,127.0.0.1,GET,http://foo.bar/old,{$time},,200,request\n");
$tokens = $this->storage->find('', '', 10, '');
$this->assertCount(1, $tokens);
$this->assertFalse($tokens[0]['has_errors'], '->find() returns has_errors=false for old CSV lines without the field');
}
public function testMultiRowIndexFile()
{
$iteration = 3;