如何避免记录由 Api 平台正确转换为状态代码的预期异常?

在我们的 Api-Platform 项目的一些路由中,throw对于一些常见的错误情况是没有异常的。

例如,在调用时POST /ordersNewOrderHandler如果合适,可以抛出这两个中的任何一个:

  • NotEnoughStock

  • NotEnoughCredit

所有这些异常都属于一个DomainException层次结构。

这些异常通过使用配置正确转换400为响应中的状态代码exception_to_status,并且响应包含适当的错误消息。到目前为止,一切都很好。

exception_to_status:
    App\Order\NotEnoughStock: !php/const Symfony\Component\HttpFoundation\Response::HTTP_BAD_REQUEST
    App\Order\NotEnoughCredit: !php/const Symfony\Component\HttpFoundation\Response::HTTP_BAD_REQUEST

唯一的问题是异常仍然被记录为CRITICAL错误,被视为“未捕获的异常”。这甚至在生产中也会被记录下来。

我本来期望通过转换为正确的状态代码(例如!== 500),这些异常将被视为“已处理”,因此不会污染日志。

从处理程序中抛出异常很方便,因为它有助于处理事务性并自动生成适当的错误响应消息。它适用于网络和控制台。

这些交易不应该被视为已处理吗?是否有必要创建另一个异常监听器来处理这个问题?如果创建异常侦听器,该怎么做才不会干扰 Api-Platform 错误规范化?


有只小跳蛙
浏览 141回答 3
3回答

慕村225694

有一个简单的答案:处理异常不是捕获异常。即使您将异常转换为 400 错误,您的异常仍未被捕获...这就是为什么 symfony 记录它并在此处完成。如果您不想记录任何内容DomainException,只需重写该logException()方法,以便在它是 DomainException 的实例时跳过记录。这是一个例子:namespace App\EventListener;use Symfony\Component\HttpKernel\EventListener\ErrorListener;class ExceptionListener extends ErrorListener{    protected function logException(\Exception $exception, string $message): void    {        if ($exception instanceof DomainException) {            return;        }        parent::logException($exception, $message);    }}最后你需要告诉 Symfony 使用这个类而不是 Symfony 的那个。由于服务定义没有类参数,exception_listener我建议使用编译器传递来替换类。namespace App;use App\EventListener\ExceptionListener;use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;use Symfony\Component\DependencyInjection\ContainerBuilder;class OverrideServiceCompilerPass implements CompilerPassInterface{    public function process(ContainerBuilder $container)    {        $definition = $container->getDefinition('exception_listener');        $definition->setClass(ExceptionListener::class);    }}有关更多详细信息,请参阅捆绑覆盖。或者,只需用您自己的服务装饰服务exception_listener,不需要编译器传递:App\EventListener\ExceptionListener:        decorates: 'exception_listener' 

慕尼黑的夜晚无繁华

我在虚拟应用程序上对其进行了测试,得到:4 月 11 日 21:36:11 |批评| REQUES Uncaught PHP Exception App\Exception\DomainException: "This is no more logged" at D:\www\campagne\src\DataPersister\StationDataPersister.php line 53 Apr 11 23:36:12 |警告 | 服务器发布 (400) /api/stations您可以实施自己的日志激活策略:本代码基于HttpCode激活策略namespace App\Loguse App\Exception\DomainException;use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy;use Symfony\Component\HttpKernel\Exception\HttpException;/** * Activation strategy for logs */class LogActivationStrategy extends ErrorLevelActivationStrategy{    public function __construct()    {        parent::__construct('error');    }    public function isHandlerActivated(array $record): bool    {        $isActivated = parent::isHandlerActivated($record);        if ($isActivated && isset($record['context']['exception'])) {            $exception = $record['context']['exception'];            // This is a domain exception, I don't log it            return !$exception instanceof DomainException;            // OR if code could be different from 400            if ($exception instanceof DomainException) {                // This is a domain exception                 // You log it when status code is different from 400.                return 400 !== $exception->getStatusCode();            }        }        return $isActivated;    }}我们还需要告诉 Monolog 使用我们的 ActivationStrategymonolog:    handlers:        main:            type: fingers_crossed            action_level: info            handler: nested            activation_strategy: App\Log\LogActivationStrategy         nested:            type: stream            path: "%kernel.logs_dir%/%kernel.environment%.log"            level: info       console:            type: console            process_psr_3_messages: false            channels: ["!event", "!doctrine", "!console"]现在我的日志只包含:4 月 11 日 23:41:07 |警告 | 服务器发布 (400) /api/stations就像@yivi,我不喜欢我的解决方案,因为每次应用程序都会尝试记录一些东西,你会在这个函数上浪费时间......而且这个方法不会改变日志,它会删除它。

catspeake

在 Monolog 中,当使用fingers_crossed日志处理程序时,将允许您从以特定状态响应的日志记录请求中排除,它只会在异常是以下实例时这样做HttpException:我通过实现订阅者将异常转换为BadRequestHttpException.final class DomainToHttpExceptionSubscriber implements EventSubscriberInterface{    public static function getSubscribedEvents(): iterable    {        return [ KernelEvents::EXCEPTION => 'convertException'];    }    public function convertException(ExceptionEvent $event): void    {        $exception = $event->getThrowable();        if ($exception instanceof DomainException) {            $event->setThrowable(                new BadRequestHttpException(                    $exception->getMessage(),                    $exception                )            );        }    }}这与这个独白配置相结合就可以了:monolog:    handlers:        fingers:              type: fingers_crossed              action_level: warning              excluded_http_codes:                - 404                - 400我从GitHub 问题的这个答案中得到了这个。它有效,但我不喜欢这个解决方案。希望有其他答案可以对此进行改进。
打开App,查看更多内容
随时随地看视频慕课网APP