为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

PHP
有只小跳蛙 2022-12-11 10:05:03
在我们的 Api-Platform 项目的一些路由中,throw对于一些常见的错误情况是没有异常的。例如,在调用时POST /orders,NewOrderHandler如果合适,可以抛出这两个中的任何一个:NotEnoughStockNotEnoughCredit所有这些异常都属于一个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 错误规范化?
查看完整描述

3 回答

?
慕村225694

TA贡献1880条经验 获得超4个赞

有一个简单的答案:处理异常不是捕获异常。


即使您将异常转换为 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' 


查看完整回答
反对 回复 2022-12-11
?
慕尼黑的夜晚无繁华

TA贡献1864条经验 获得超6个赞

我在虚拟应用程序上对其进行了测试,得到:

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\Log


use 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 使用我们的 ActivationStrategy


monolog:

    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,我不喜欢我的解决方案,因为每次应用程序都会尝试记录一些东西,你会在这个函数上浪费时间......而且这个方法不会改变日志,它会删除它。


查看完整回答
反对 回复 2022-12-11
?
catspeake

TA贡献1111条经验 获得超0个赞

在 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 问题的这个答案中得到了这个。它有效,但我不喜欢这个解决方案。希望有其他答案可以对此进行改进。


查看完整回答
反对 回复 2022-12-11
  • 3 回答
  • 0 关注
  • 138 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信