symfony: [Console][ErrorHandler] Add option to disable deprecations in dev console in 4.4

Description
After running any command in dev (debug=1) I get a lot of messages in stdout like:

2020-02-03T16:37:57+01:00 [info] User Deprecated: The "Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand" class is deprecated since Symfony 4.2, use "Symfony\Component\Console\Command\Command" with dependency injection instead.

Console screen gets a bit messy.

That’s very helpful but I don’t need it every time I run a console command. Right now the only workaround is to disable debug, because the error_reporting setting is ignored.

Previously (4.2) I just used an option in framework.yaml:

framework:
    php_errors:
        log: 4096

It was enough to get rid of those deprecations.

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 17
  • Comments: 71 (51 by maintainers)

Most upvoted comments

Correct, SYMFONY_DEPRECATIONS_HELPER will only have any effect when running via the symfony phpunit helper.

A revised version of @ju1ius solution but with an opt in/out flag:

class Kernel extends BaseKernel
{
    use MicroKernelTrait;

    public function boot(): void
    {
        parent::boot();

        // https://github.com/symfony/symfony/issues/35575
        if ($_SERVER['APP_DEBUG']) {
            $showDeprecations = $_ENV['APP_DEPRECATIONS'] ?? $_SERVER['APP_DEPRECATIONS'] ?? false;
            $showDeprecations = filter_var($showDeprecations, FILTER_VALIDATE_BOOLEAN);

            if (!$showDeprecations) {
                ErrorHandler::register(null, false)->setLoggers([
                    \E_DEPRECATED => [null],
                    \E_USER_DEPRECATED => [null],
                ]);
            }
        }
    }
}

Hi,

Is there any config to make the [info] User Deprecated: disappear when running a console command in debug ?

Running Symfony 5.4

Thanks in advance for your help, Julien

This burning our prods and sending it into hell, especially with messenger in conjunction with fingers_crossed handler which is broken (but that’s another topic, rather difficult to solve topic, and I get that).

I did some forensics and found that in there:

https://github.com/symfony/symfony/blob/master/src/Symfony/Component/ErrorHandler/ErrorHandler.php#L411

i.e.:

        $level = error_reporting();
        $silenced = 0 === ($level & $type);
        // Strong errors are not authorized to be silenced.
        $level |= E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED;

Strong errors are not authorized to be silenced

It’s a matter of opinion, I don’t see a deprecation notice as a strong error.

The error handler component refuses explicitly to honour our error reporting configuration, without any kind of valid argument.

Deprecations should not get into log into production.

A workaround without the need to implement an handler class is to just call…

// calls setLoggers on the currently registered handler instance
ErrorHandler::register(null, false)->setLoggers([
    \E_DEPRECATED => [null],
    \E_USER_DEPRECATED => [null],
]);

…at the appropriate place in your application boot sequence.

For example in your AppKernel:

namespace App;

use Symfony\Component\HttpKernel\Kernel as BaseKernel;

class Kernel extends BaseKernel
{
    public function boot()
    {
        parent::boot();
        ErrorHandler::register(null, false)->setLoggers([
            \E_DEPRECATED => [null],
            \E_USER_DEPRECATED => [null],
        ]);
    }
}

I’d like to share what we ended up doing, for those out there that might be interested on how to work around your console commands getting polluted with deprecation notices which you have no control over.

We created our own custom debug error handler, which is essentially a copy of the original debug handler with an additional flag to opt-in/out of the console deprecations. We then modified our bin/console file to use our custom debug error handler instead of the default one.

MyApp\DebugErrorHandler


declare(strict_types=1);

use Symfony\Component\ErrorHandler\BufferingLogger;
use Symfony\Component\ErrorHandler\DebugClassLoader;
use Symfony\Component\ErrorHandler\ErrorHandler;

/**
 * This class is a copy of the original Symfony\Component\ErrorHandler\Debug
 * class with the difference that it sets a null logger for deprecations.
 * At the time of writing it's still impossible to silence deprecations in the
 * symfony console which leads to noisy output when running console commands
 * with debug enabled.
 *
 * For more information see https://github.com/symfony/symfony/issues/35575.
 */
class DebugErrorHandler
{
    public static function enable(bool $showDeprecations = true): ErrorHandler
    {
        // (... original code from `Symfony\Component\ErrorHandler\ErrorHandler\Debug` ...)

        $errorHandler = new ErrorHandler(new BufferingLogger(), true);
        if (!$showDeprecations) {
            // silence deprecations in the console
            $errorHandler->setLoggers([
                \E_DEPRECATED => null,
                \E_USER_DEPRECATED => null,
            ]);
        }

        return ErrorHandler::register($errorHandler);
    }
}

bin/console

(...)

if ($_SERVER['APP_DEBUG']) {
    umask(0000);

    if (class_exists(Debug::class)) {
        // use custom debug error handler instead of default one
        // https://github.com/symfony/symfony/issues/35575
        $showDeprecations = $_ENV['APP_CONSOLE_DEPRECATIONS'] ?? $_SERVER['APP_CONSOLE_DEPRECATIONS'] ?? false;
        $showDeprecations = filter_var($showDeprecations, \FILTER_VALIDATE_BOOLEAN);
        DebugErrorHandler::enable($showDeprecations);
    }
}

By default deprecations will not be shown in the console, if one wishes to see them just set the APP_CONSOLE_DEPRECATIONS environment variable to a truthy value.

Hopefully someone else in this thread will also find it useful. Cheers!

Correct, SYMFONY_DEPRECATIONS_HELPER will only have any effect when running via the symfony phpunit helper.

A revised version of @ju1ius solution but with an opt in/out flag:

class Kernel extends BaseKernel
{
    use MicroKernelTrait;

    public function boot(): void
    {
        parent::boot();

        // https://github.com/symfony/symfony/issues/35575
        if ($_SERVER['APP_DEBUG']) {
            $showDeprecations = $_ENV['APP_DEPRECATIONS'] ?? $_SERVER['APP_DEPRECATIONS'] ?? false;
            $showDeprecations = filter_var($showDeprecations, FILTER_VALIDATE_BOOLEAN);

            if (!$showDeprecations) {
                ErrorHandler::register(null, false)->setLoggers([
                    \E_DEPRECATED => [null],
                    \E_USER_DEPRECATED => [null],
                ]);
            }
        }
    }
}

It would be great if this was implemented by default, maybe you could open a PR? I would be more than happy to provide a PR if you don’t have the time.

This issue is been dragging for well over 2 years now, time for some fixes 😃

I’m sorry also to highlight this but the tone is so stressful here that it’s hard to get into anything constructive. “burning”, “broken”, “forensic”, “refuses explicitly to honour”, “without any kind of valid argument”, “Once again, I repeat”, “Even worse”, “Fun thing”, “This is much worse”, “seriously inconsistent, unexpected and awkward behaviour.”, “do not attempt to be smarter”.

Please keep a cool head. All those words are needlessly dramatic and emotionally loaded. This is even worse when they claim something that happens to be wrong: the line that is yelled at is likely not related, since the error level is always zero for deprecations.

@pounard I did understand that you’d like to handle deprecations separately from other PHP notices, yet you wonder how to do so since right now the php channel mixes both at the info level. It looks like a feature request, nice one.

@jkobus I’m not sure anymore if @pounard’s request would fix your case. Please tell if this is being hijacked or not from your pov 😃

I have the same question – I can’t see the output of my console commands, frequently $logger->info(“something I want to see”), and I feel like I’ve tried everything in this long thread. Also running Symfony 5.4.

monolog:
    channels: [deprecation]
    handlers:
        deprecation:
            type: stream
            level: emergency
            channels: [deprecation]
            path: "%kernel.logs_dir%/%kernel.environment%.deprecations.log"
        main:
            type: stream
            path: "%kernel.logs_dir%/%kernel.environment%.log"
            level: warning
            channels: ["!event"]

Most of these errors are related to not having a return type, and they’re from bundles I have no control over.

The problem is almost 3 years old and not solved, it says a lot about the core team.

I don’t see a pull request created by you that would this three year old issue. It says a lot about you.

Set the following env variable (eg. in .env.local):

SYMFONY_DEPRECATIONS_HELPER=weak

I’ve tested almost all suggestions here, but none worked with Symfony 6.0 and monolog-bundle 3.7.1. It’s sad because the deprecations are in third-party libraries which I’m not the owner of, and I really don’t care if they use a deprecated class that is inside a famous project. Also, I can’t see any reason for Symfony forcing us to deal with deprecations if we don’t ask for that.

That being said, my workaround is to redirect the stderr to nowhere:

$ ./bin/console MY_COMMAND 2>/dev/null

@pounard that’s easier said than done for many of us. I for one have a project where most of the deprecations do not come from our code directly but rather from upstream dependencies which have yet to adapt their code to be deprecation free. So sadly, unless one is willing to create a bunch of forks with custom patches, there’s really not much else that can be done.

OK here is where I got so far:

  • When the debug component is enabled, during bootstrap, an instance of BufferingLogger is set as default logger, in Debug::enable(), before the kernel is created by calling ErrorHandler::setDefaultLogger().
  • Then ErrorHandler::register() is called right after that (OK, it wants to capture errors before monolog is initialized, I guess, seems legit).
  • Then during FrameworkBundle::boot() the error handler is set once again (seems legit at this point).
  • On ConsoleEvents::COMMAND event, DebugHandlersListener::configure() method is called which calls itself DebugHandlersListener::setDefaultLoggers(), which sets a deprecation logger by calling ErrorHandler::setDefaultLogger() once again.
  • On ErrorHandler::setDefaultLogger() calls setLoggers() which itself will call $this->bootstrappingLogger->cleanLogs(), which will fetch all errors that were buffered in the BufferingLogger.
  • This calls Symfony\Bridge\Monolog\Logger::addRecord() for each log entry.
  • At the point, $record array has the right channel and level set, which is good.
  • It iterates over all handlers and call the handle() method, most handlers will only check for the error level, not the channel, which makes all of them log the error.
  • I lost track of were I was debugging, but going back to DebugHandlersListener::setDefaultLoggers() which was not ended yet, it calls ErrorHandler::setDefaultLogger() a second time, at the end of the method.
  • Which calls ErrorHandler::setLogger() a second time, which then decide to $flush because loggers have changed, but this time $this->bootstrappingLogger->cleanLogs() yields no logs.
  • Then going back to DebugHandlersListener::configure(), please note I still do not have anything in console displaying yet (either it’s not flushed yet, either logs have not been sent yet).
  • Then it goes out of the event dispatcher, run the command, and logs appears when the command is run.

The debug machinery is terribly complicated (and honestly, code is far from being easily read), I’m stopping here. I suspect that when cleanLogs() is called for the first time, loggers are not fully setup yet (at this very moment, it seems that the ErrorHandler yield only one Logger instance), and this is why all messages end up being flushed in console instead of being rightfully dispatched in the right channel handlers.

At this very moment, $type value is 16384, and only 8192 (E_DEPRECATED) and 16384 (E_USER_DEPRECATED) have a configured logger, all other types have the default BufferingLogger.

This means that something, during initialization, forces all the deprecations to have a configured logger which ignores channels and sends everything to every handler it has at this point. But at this very moment, handlers in memory are not the one configured by the user configuration, because framework is not bootstrapped yet.

I think this is a bug, and the bug lies in DebugHandlersListener::configure(), it should not call setDefaultLoggers() which forces to flush deprecation errors that happened during initialization (before monolog was correctly setup) because at this moment, monolog is not completely initialized yet. There’s something wrong happening in there.

But in the end, this is not a critical bug, since it happens only when APP_DEBUG is true.

As a side note, the whole DebugHandlersListener is very confusing, and very hard to understand, I wouldn’t be surprised if there is some confusion in logic inside.

@nicolas-grekas, @pounard, I tested this again with symfony 4.4.8.

The issue is with this setting:

framework:
    php_errors:
        log: true

By default, it’s now true (in documentation it states, that it defaults to kernel.debug, and thats not true, as it’s true 😄 ). This makes the app to fill production log with deprecations.

To change that behavior, one can try to set mentioned value like this:

packages/framework.yaml

framework:
    php_errors:
        log: 4096 # log only above deprecations - see https://www.php.net/manual/en/errorfunc.constants.php

Now, prod.log is safe, but logs will now appear in console (dev, debug=true). The reason is that this setting will also prevent writing deprecations to dev.log and - I guess, any logs left in buffer are just written to stdout, which is reasonable as logs are not lost in buffer.

So the workaround here is to have two framework.yaml files:

packages/framework.yaml

framework:
    php_errors:
        log: true

This will write deprecations to log, leaving the console clean.

packages/prod/framework.yaml

framework:
    php_errors:
        log: 4096

This will keep prod.log clean from deprecations. This actually solves my issue.

Hello,

i have the same message about “deprecation” in my console for 5.4.10 project even with APP_ENV=prod / APP_DEBUG=0 and a “default” monolog.yaml when clearing cache.

Only way i found is to change this in framework.yaml

framework:
    php_errors:
        log: 4096

Isn’t SYMFONY_DEPRECATIONS_HELPER just phpunit-specific?

In Symfony 5.x this worked - mutes the deprecations totally:

    deprecation:
      type: stream
      level: emergency

I sincerely think that this line:

// Strong errors are not authorized to be silenced.
$level |= E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED;

In the ErrrorHandler::handleError() should just be removed. Please let developers and sysadmin configure their own environments, do not attempt to be smarter than them with this.

Huge canvases displayed every time the command is called is not the norm, it repels newcomers who start using Symfony. I have Symfony 6 & PHP 8.1 versions and the same thing happens. The problem is almost 3 years old and not solved, it says a lot about the core team.

that’s easier said than done for many of us.

Yes, I definitely agree, I didn’t fix anything myself yet on my projects, it’s an insane amount of work, almost impossible because of external dependencies. I was being sarcastic.

I completely get the frustration with this issue. Seeing hundreds of lines of deprecation warnings when using the console is obnoxious and doesn’t make much sense. Deprecation messages are useful but only in certain contexts. The console definitely isn’t one of them (in my view at least). This is worsened significantly by the fact that the bulk of those messages come from third party code that can’t be reasonably fixed.

Personally, I’d love to fix this issue on my own but I don’t know nearly enough about the complete system to feel confident enough to overhaul or fix it to be more flexible. Given that this issue has been around for as long as it has, I’m clearly not alone in that.

The workaround in the boot() kernel function has mostly eliminated the problem for me. Specifically, the modified version @nocive posted in June. There are still situations when using the console where it just spits out hundreds of deprecation warnings, and that’s not particularly fun. But it’s mostly been eliminated.

I believe if you register a deprecations channel in your monolog configuration, all deprecations will be logged there. Then you can exclude them from the console, just like other channels. But that is with 6.2, not sure if this is related- or backported to 4.4.

Not sure if this was already the case back then or if it works though but I’ll give it a go. If that works, this issue could be marked as resolved I think, unless the documentation needs to be updated?

Could you please share boot() workaround? I am completely frustrated with this. It is a bad joke that PHP and Symfony does not give a straightforward way to disable such things. Error handling is a mess in PHP. What a pity.

https://github.com/symfony/symfony/issues/35575#issuecomment-1164178596

@xabbuh whereas I understand why you’d defend the core team (as I would I guess because Symfony is huge and great) I do think all the confusion around logging is due to the fact there’s probably no-one fully understanding the whole debug, monolog and error handler code at once. I do think, without any disrespect to core team member, it’s open source, that many people did work on this, but never one person on the whole chain, which makes it a very hard to understand piece of historical spaghetti code. In my opinion, all code around early logging should be trashed and rewrote from scratch simpler.

That’s I guess what one would call “technical debt”.

We have the same thing, none of the above worked. For now fixed locally with this in php.ini:

[PHP]
error_reporting = E_RECOVERABLE_ERROR

Still shows the deprecations in profiler. But no longer annoying messages on your bin/console.

I’ve tested almost all suggestions here, but none worked with Symfony 6.0 and monolog-bundle 3.7.1. It’s sad because the deprecations are in third-party libraries which I’m not the owner of, and I really don’t care if they use a deprecated class that is inside a famous project. Also, I can’t see any reason for Symfony forcing us to deal with deprecations if we don’t ask for that.

That being said, my workaround is to redirect the stderr to nowhere:

$ ./bin/console MY_COMMAND 2>/dev/null

Same, which is a shame as like you say, I fixed all deprecations I could find and all deprecations are from third party packages which are already at their latest versions.

Was hoping the monolog fix would work but no.

Since I upgraded a project from 5.3 to 5.4, deprecations are back again in console. I tried many subtile ways of configuring monolog, nothing is working. I hoped I would not need to go into step debug again, but I’ll do.

I’ll add my two cents here because I am experimenting the same kind of issue under 4.x.

On dev env running under Docker, I have hundreds of deprecation line each time I refresh a page:

NOTICE: PHP message: 2021-12-06T14:26:01+01:00 [info] User Deprecated: The "templating" service is deprecated since Symfony 4.4 and will be removed in 5.0.
NOTICE: PHP message: 2021-12-06T14:26:01+01:00 [info] User Deprecated: The Symfony\Bundle\TwigBundle\TwigEngine class is deprecated since version 4.3 and will be removed in 5.0; use \Twig\Environment instead.
NOTICE: PHP message: 2021-12-06T14:26:01+01:00 [info] User Deprecated: The Symfony\Bridge\Twig\TwigEngine class is deprecated since version 4.3 and will be removed in 5.0; use \Twig\Environment instead.
NOTICE: PHP message: 2021-12-06T14:26:01+01:00 [info] User Deprecated: The Symfony\Bundle\FrameworkBundle\Templating\EngineInterface interface is deprecated since version 4.3 and will be removed in 5.0; use Twig instead.

As for any PHP application, I start by using error_reporting to remove the deprecation report on the public/index.php, before kernel loading.

I also tried configuring php-fpm directly with the log_level = warning directive.

And finally, the php_errors configuration option proposed by @jkobus

Nothing worked, and indeed, removing the E_DEPRECATED | E_USER_DEPRECATED part of the ErrorHandler “resolves” the issue, as reported by @pounard. There is also the debug disabling possibility, but that is not an acceptable solution as it will not only disable the error reporting.

I also agree that deprecation warnings are great, especially on the profiler report. However, having hundreds of deprecation warnings per request on our local dev output give nothing else expect an unusable log.

I read there is a possible configuration with Symfony 5. However I do not understand: Why can’t the “Strong errors” part be configurable? As you override the possibility to directly manage error_reporting, it would be great at least to give a control back door for the end developer.

Or am I also missing something?

Best regards

@Volmarg where do you put these values ?

./config/packages/monolog.yaml

monolog:
  handlers:
    deprecation:
      type: stream
      level: emergency

Also Symfony 5 has this

./config/packages/prod/deprecations.yaml

# As of Symfony 5.1, deprecations are logged in the dedicated "deprecation" channel when it exists
monolog:
    channels: [deprecation]
    handlers:
        deprecation:
            type: stream
            channels: [deprecation]
            path: php://stderr

So this is probably why it works

The problem still exists in symfony 4.4.30.

@pounard, in production environment this problem should not appear.

This depends on the APP_DEBUG env variable, which should be 0 on production bu default.

bin/console:

if ($_SERVER['APP_DEBUG']) {
    umask(0000);

    if (class_exists(Debug::class)) {
        Debug::enable();
    }
}

In short, the problem appears - in my case, when using console like this:

php bin/console --env=dev // local development
APP_DEBUG=1 php bin/console --env=prod