Summary
ErrorListener::duplicateRequest() logs a message describing normal operation at error level:
https://github.com/api-platform/core/blob/main/src/Symfony/EventListener/ErrorListener.php#L90-L92
if ($this->debug) {
$this->logger?->error('An exception occured, transforming to an Error resource.', ['exception' => $exception, 'operation' => $apiOperation]);
}
The call is already gated behind $this->debug, and the message states what the listener is about to do rather than reporting a problem. error seems one or two levels too high for it.
This is a papercut, not a behavioural bug — the response produced is entirely correct.
Why it is worth changing
Without monolog-bundle, Symfony falls back to Symfony\Component\HttpKernel\Log\Logger, writing to stderr. The phpunit/phpunit recipe in symfony/recipes ships a phpunit.xml.dist containing <server name="SHELL_VERBOSITY" value="-1" />, and that value puts the fallback logger's threshold at exactly error (see Logger::__construct()).
So this affects any project on the standard Symfony test setup without Monolog.
The result is that any functional test asserting a 4xx prints this line in the middle of an otherwise green run:
PHPUnit 11.5.56 by Sebastian Bergmann and contributors.
............................................[error] An exception occured, transforming to an Error resource.
..................... 66 / 66 (100%)
OK (66 tests, 225 assertions)
A line saying [error] in a passing suite reads like a failure. In our case it was reported internally as "the test suite throws an error", and it took reading the listener's source to establish that nothing was wrong.
More generally: reserving error for things that actually are errors is what keeps the level useful.
Symfony itself takes this line in ErrorListener::resolveLogLevel() — critical for 5xx, error for other exceptions — and exposes framework.exceptions[].log_level so applications can reclassify client errors. That configuration cannot reach this call, because the level is hardcoded here.
Reproduction
Any functional test that asserts an exception-derived response with APP_DEBUG=1. Ours asserts that a user lacking ROLE_ADMIN gets a 403 on /docs:
$client->request('GET', '/docs', [
'headers' => ['Accept' => 'text/html', 'Authorization' => 'Bearer '.$token],
]);
self::assertResponseStatusCodeSame(403);
Passes, and prints the line. Setting APP_DEBUG=0 removes it, confirming it is debug-gated.
Suggested change
Use debug() — or info() — instead of error(). The if ($this->debug) guard already limits it to development.
if ($this->debug) {
$this->logger?->debug('An exception occured, transforming to an Error resource.', ['exception' => $exception, 'operation' => $apiOperation]);
}
Happy to send a PR if you would take one.
(Minor aside, entirely optional: "occured" is missing an r.)
Versions
|
|
| API Platform |
4.3.17 (line is unchanged on main) |
| Symfony |
8.1 |
| PHP |
8.5.7 |
| Install |
api-platform/symfony |
Summary
ErrorListener::duplicateRequest()logs a message describing normal operation aterrorlevel:https://github.com/api-platform/core/blob/main/src/Symfony/EventListener/ErrorListener.php#L90-L92
The call is already gated behind
$this->debug, and the message states what the listener is about to do rather than reporting a problem.errorseems one or two levels too high for it.This is a papercut, not a behavioural bug — the response produced is entirely correct.
Why it is worth changing
Without
monolog-bundle, Symfony falls back toSymfony\Component\HttpKernel\Log\Logger, writing to stderr. Thephpunit/phpunitrecipe insymfony/recipesships aphpunit.xml.distcontaining<server name="SHELL_VERBOSITY" value="-1" />, and that value puts the fallback logger's threshold at exactlyerror(seeLogger::__construct()).So this affects any project on the standard Symfony test setup without Monolog.
The result is that any functional test asserting a 4xx prints this line in the middle of an otherwise green run:
A line saying
[error]in a passing suite reads like a failure. In our case it was reported internally as "the test suite throws an error", and it took reading the listener's source to establish that nothing was wrong.More generally: reserving
errorfor things that actually are errors is what keeps the level useful.Symfony itself takes this line in
ErrorListener::resolveLogLevel()—criticalfor 5xx,errorfor other exceptions — and exposesframework.exceptions[].log_levelso applications can reclassify client errors. That configuration cannot reach this call, because the level is hardcoded here.Reproduction
Any functional test that asserts an exception-derived response with
APP_DEBUG=1. Ours asserts that a user lackingROLE_ADMINgets a 403 on/docs:Passes, and prints the line. Setting
APP_DEBUG=0removes it, confirming it is debug-gated.Suggested change
Use
debug()— orinfo()— instead oferror(). Theif ($this->debug)guard already limits it to development.Happy to send a PR if you would take one.
(Minor aside, entirely optional: "occured" is missing an
r.)Versions
main)api-platform/symfony