Skip to content

ErrorListener logs a debug-only diagnostic at error level #8432

Description

@davidmpaz

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions