【问题标题】:How to catch and log all exceptions in an Apigility ZF2 application?如何在 Apigility ZF2 应用程序中捕获和记录所有异常?
【发布时间】:2015-06-08 22:25:19
【问题描述】:

我想在 Apigility Zend Framework 2 应用程序中构建错误处理和日志记录机制,并捕获和记录所有异常。

经过一番研究,我发现了一个 Stack Overflow answer 的解决方案,它似乎完全符合这个要求。这是答案中的代码(有一些小的命名和格式修改):

Module.php

...

use Zend\Mvc\ModuleRouteListener;
use Zend\Log\Logger;
use Zend\Log\Writer\Stream;

...

class Module implements ApigilityProviderInterface
{

    public function onBootstrap(MvcEvent $mvcEvent)
    {
        $eventManager = $mvcEvent->getApplication()->getEventManager();
        $moduleRouteListener = new ModuleRouteListener();
        $moduleRouteListener->attach($eventManager);
        /**
         * Log any Uncaught Exceptions, including all Exceptions in the stack
         */
        $sharedEventManager = $mvcEvent->getApplication()->getEventManager()->getSharedManager();
        $serviceManager = $mvcEvent->getApplication()->getServiceManager();
        $sharedEventManager->attach('Zend\Mvc\Application', MvcEvent::EVENT_DISPATCH_ERROR,
            function($mvcEvent) use ($serviceManager) {
                if ($mvcEvent->getParam('exception')){
                    $exception = $mvcEvent->getParam('exception');
                    do {
                        $serviceManager->get('Logger')->crit(
                            sprintf(
                               "%s:%d %s (%d) [%s]\n", 
                                $exception->getFile(), 
                                $exception->getLine(), 
                                $exception->getMessage(), 
                                $exception->getCode(), 
                                get_class($exception)
                            )
                        );
                    }
                    while($exception = $exception->getPrevious());
                }
            }
        );
    }

    ...

    public function getServiceConfig() {
        return array(
            'factories' => array(
                // V1
                ...
                'Logger' => function($sm){
                    $logger = new Logger;
                    $writer = new Stream('/var/log/httpd/sandbox-log');
                    $logger->addWriter($writer);
                    return $logger;
                },
            ),
            ...
        );
    }

}

所以现在我已经在代码中的几个地方(ResourceServiceMapper 类)尝试了这个(用一个简单的throw new \Exception('foo'))并期望得到缓存的异常并登录到我定义的文件中。但它不起作用。

我做错了吗?什么?如何让它工作? 如何在 Apigility 驱动的 Zend Framework 2 应用程序中捕获和记录所有异常?


附加信息:代码中引发异常的位置示例:

class AddressResource extends AbstractResourceListener ...
{
    public function fetch($id) {
        throw new \Exception('fetch_EXCEPTION');
        $service = $this->getAddressService();
        $entity = $service->getAddress($id);
        return $entity;
    }
}

附加信息:响应中的跟踪(如果在BarResource#fetch(...) 中设置throw new \Exception('fetch_EXCEPTION');):

{
    "trace": [
        {
            "file": "/var/www/my-project/vendor/zfcampus/zf-rest/src/AbstractResourceListener.php",
            "line": 166,
            "function": "fetch",
            "class": "FooAPI\\V1\\Rest\\Bar\\BarResource",
            "type": "->",
            "args": [
                "1"
            ]
        },
        {
            "function": "dispatch",
            "class": "ZF\\Rest\\AbstractResourceListener",
            "type": "->",
            "args": [
                {}
            ]
        },
        {
            "file": "/var/www/my-project/vendor/zendframework/zendframework/library/Zend/EventManager/EventManager.php",
            "line": 444,
            "function": "call_user_func",
            "args": [
                [
                    {},
                    "dispatch"
                ],
                {}
            ]
        },
        {
            "file": "/var/www/my-project/vendor/zendframework/zendframework/library/Zend/EventManager/EventManager.php",
            "line": 205,
            "function": "triggerListeners",
            "class": "Zend\\EventManager\\EventManager",
            "type": "->",
            "args": [
                "fetch",
                {},
                {}
            ]
        },
        {
            "file": "/var/www/my-project/vendor/zfcampus/zf-rest/src/Resource.php",
            "line": 541,
            "function": "trigger",
            "class": "Zend\\EventManager\\EventManager",
            "type": "->",
            "args": [
                {},
                {}
            ]
        },
        {
            "file": "/var/www/my-project/vendor/zfcampus/zf-rest/src/RestController.php",
            "line": 483,
            "function": "fetch",
            "class": "ZF\\Rest\\Resource",
            "type": "->",
            "args": [
                "1"
            ]
        },
        {
            "file": "/var/www/my-project/vendor/zendframework/zendframework/library/Zend/Mvc/Controller/AbstractRestfulController.php",
            "line": 366,
            "function": "get",
            "class": "ZF\\Rest\\RestController",
            "type": "->",
            "args": [
                "1"
            ]
        },
        {
            "file": "/var/www/my-project/vendor/zfcampus/zf-rest/src/RestController.php",
            "line": 332,
            "function": "onDispatch",
            "class": "Zend\\Mvc\\Controller\\AbstractRestfulController",
            "type": "->",
            "args": [
                {}
            ]
        },
        {
            "function": "onDispatch",
            "class": "ZF\\Rest\\RestController",
            "type": "->",
            "args": [
                {}
            ]
        },
        {
            "file": "/var/www/my-project/vendor/zendframework/zendframework/library/Zend/EventManager/EventManager.php",
            "line": 444,
            "function": "call_user_func",
            "args": [
                [
                    {},
                    "onDispatch"
                ],
                {}
            ]
        },
        {
            "file": "/var/www/my-project/vendor/zendframework/zendframework/library/Zend/EventManager/EventManager.php",
            "line": 205,
            "function": "triggerListeners",
            "class": "Zend\\EventManager\\EventManager",
            "type": "->",
            "args": [
                "dispatch",
                {},
                {}
            ]
        },
        {
            "file": "/var/www/my-project/vendor/zendframework/zendframework/library/Zend/Mvc/Controller/AbstractController.php",
            "line": 118,
            "function": "trigger",
            "class": "Zend\\EventManager\\EventManager",
            "type": "->",
            "args": [
                "dispatch",
                {},
                {}
            ]
        },
        {
            "file": "/var/www/my-project/vendor/zendframework/zendframework/library/Zend/Mvc/Controller/AbstractRestfulController.php",
            "line": 300,
            "function": "dispatch",
            "class": "Zend\\Mvc\\Controller\\AbstractController",
            "type": "->",
            "args": [
                {},
                {}
            ]
        },
        {
            "file": "/var/www/my-project/vendor/zendframework/zendframework/library/Zend/Mvc/DispatchListener.php",
            "line": 93,
            "function": "dispatch",
            "class": "Zend\\Mvc\\Controller\\AbstractRestfulController",
            "type": "->",
            "args": [
                {},
                {}
            ]
        },
        {
            "function": "onDispatch",
            "class": "Zend\\Mvc\\DispatchListener",
            "type": "->",
            "args": [
                {}
            ]
        },
        {
            "file": "/var/www/my-project/vendor/zendframework/zendframework/library/Zend/EventManager/EventManager.php",
            "line": 444,
            "function": "call_user_func",
            "args": [
                [
                    {},
                    "onDispatch"
                ],
                {}
            ]
        },
        {
            "file": "/var/www/my-project/vendor/zendframework/zendframework/library/Zend/EventManager/EventManager.php",
            "line": 205,
            "function": "triggerListeners",
            "class": "Zend\\EventManager\\EventManager",
            "type": "->",
            "args": [
                "dispatch",
                {},
                {}
            ]
        },
        {
            "file": "/var/www/my-project/vendor/zendframework/zendframework/library/Zend/Mvc/Application.php",
            "line": 314,
            "function": "trigger",
            "class": "Zend\\EventManager\\EventManager",
            "type": "->",
            "args": [
                "dispatch",
                {},
                {}
            ]
        },
        {
            "file": "/var/www/my-project/public/index.php",
            "line": 56,
            "function": "run",
            "class": "Zend\\Mvc\\Application",
            "type": "->",
            "args": []
        }
    ],
    "type": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html",
    "title": "Internal Server Error",
    "status": 500,
    "detail": "fetch_EXCEPTION"
}

【问题讨论】:

  • 问题是没有到达回调函数还是日志没有工作?您是否尝试过以更高的优先级附加您的听众,即> 1
  • 没错,回调根本没有到达。它适用于另一个 ZF2 应用程序,但在这里,在“Apigility 应用程序”中,它似乎被忽略了。我刚刚尝试更改优先级(1000-1000),但没有帮助。

标签: logging exception-handling zend-framework2 laminas-api-tools exception-logging


【解决方案1】:

我们目前正在使用以下代码成功捕获来自 Apigility 的所有错误响应:

$app = $event->getTarget();
$em = $app->getEventManager();

$sendResponseListener = $app->getServiceManager()->get('SendResponseListener');
$sendResponseListener->getEventManager()->attach(SendResponseEvent::EVENT_SEND_RESPONSE,  function(SendResponseEvent $event) {
    $response = $event->getResponse();
    if ($response instanceof ApiProblemResponse) {
          $error = $response->getApiProblem()->toArray();
          // inspect $error array and log the information you want
    }
});

【讨论】:

  • @automatix 不幸的是,由于 Apigility 的糟糕设计,这是记录 API 资源侦听器引发的异常的唯一方法。这是因为\ZF\Rest\RestController 捕获任何异常并通过返回ApiProblemResponse处理它们。所以dispatch.error事件永远不会被触发,也不会发生正常的ZF异常处理。
  • 对此略有改进:您实际上可以只附加到标准 Application 对象上的 finish 事件,而不需要从服务管理器获取 SendResponseListener。 IE。 $e->getApplication()->getEventManager()->attach(MvcEvent::EVENT_FINISH, function() {...}
【解决方案2】:

您已将侦听器连接到 MvcEvent::EVENT_DISPATCH_ERROR 事件。您确定在调度期间(即在控制器中)抛出您的new \Exception('foo')。同样在the answer you linked to 中,他们提到此解决方案用于捕获控制器中抛出的错误/异常。

如果您在渲染时抛出异常,您的监听器将永远不会被触发。在这些情况下,您需要收听MvcEvent::EVENT_RENDER_ERROR

我想知道这种设置是否是最好的方法。也许您应该搜索其他示例,而不是简单地跟随/复制 StackOverflow 的答案。

编辑:

如果您还使用the ApiProblem module for Apigility,则可能是the ApiProblemListener 在您自己的侦听器之前的MvcEvent::EVENT_DISPATCH_ERROR 事件上被触发。

the onDispatchError method 中,ApiProblemListener 返回一个响应对象,这可能是其他事件(优先级较低)在此之后根本不会触发的原因。

ApiProblemListener 是这样附加的:

$this->listeners[] = $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, array($this, 'onDispatchError'), 100);

尝试将侦听器的优先级从ApiProblemListener 提高到高于100 的值。那么你可能会成功。

【讨论】:

  • 感谢您的回答!我会逐点回复: 1. EVENT_DISPATCH_ERROR 是正确的事件,至少它可以在另一个应用程序中工作(没有 Apigility)。 2. 控制器:不仅是控制器,它也可以在模型类中工作(在另一个没有 Apigility 的应用程序中)。 3.“复制答案”:我复制了代码,IMO应该可以工作;这种方法也在其他资源中有所描述,例如herehere.
  • 这是一个很好的提示,非常感谢!是的,似乎是这样,ApiProblemListener 捕获异常并禁止其他侦听器进行理论处理。 priority 设置为 100 (class ApiProblemListener { ... public function attach(EventManagerInterface $events) { ... $this->listeners[] = $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, array($this, 'onDispatchError'), 100); ... } ... })。但即使我为我的听众设置了更高的priority(例如10000),ApiProblem“获胜”并且我的听众会被忽略。
  • 等等,这不是ApiProblem。我在ApiProblemListener#onDispatchError(...) 中设置了断点,甚至在那里写了一个die()——该方法不会被调用。所以,它一定是另一个听众。
  • 你在哪里抛出你的异常?如果我在我的RestController 实例中抛出一个异常,它最终会调用ApiProblemListener 中的onDispatchError 方法,所以在某个地方你正在做一些不同的事情。也许你可以解释一下你是如何以及在哪里测试你的代码的。
  • 首先感谢您仍然努力提供帮助!对于您的问题:我在哪里抛出异常以及如何测试代码。在我的应用程序中,我使用了一个结构(如所示的here),调用链看起来像AddressResource#fetchAll(...) -> AddressService#getBar(...) -> AddressMapper#findAll(...)。我试图在每个步骤中抛出异常。另请参阅我的问题更新中的示例。为了测试日志记录,我只是观察Logger 应该将信息存储到(tail -f /var/log/httpd/sandbox-log)的文件。
【解决方案3】:

我在记录任何发生的异常时面临同样的问题,

我尝试将我自己的听众附加到MvcEvent::EVENT_DISPATCH_ERROR,即使高优先级[3000] 不起作用,经过一些研究并阅读Apigility 代码后我发现 任何抛出的异常都将被ApiProblemListener 捕获并从这个​​异常信息中创建新的ApiProblemonRender method

解决问题和记录任何异常的解决方法是覆盖 AbstractResourceListener 并创建自己的 ResourceListener 并强制任何资源类扩展它,

你自己的ResourceListener 必须重写dispatch 方法并调用父方法来捕获抛出的异常并记录它然后返回新的响应

示例:

<?php

namespace API\V1\Listener;

class MyOwnListener  extends AbstractResourceListener
{

    /**
     * {@inheritdoc}
     */
    public function dispatch(ResourceEvent $event)
    {
        try {
            $response = parent::dispatch($event);
        } catch (\Throwable $exception) {
            // catch thrown exception
            // then return new APIProblem with message you want 
            $response = new ApiProblem(500, 'error message');
        }
        return $response;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-15
    • 2010-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-03
    • 2019-03-02
    相关资源
    最近更新 更多