Как перехватить и зарегистрировать все исключения в приложении Apigility ZF2?

Я хочу встроить механизм обработки ошибок и протоколирования в приложение Apigility Zend Framework 2 и перехватывать и регистрировать все исключения.

После некоторого исследования я нашел решение Stack Overflow с решением, которое, казалось, точно соответствовало этим требованиям. Вот код из ответа (с некоторыми незначительными изменениями именования и форматирования):

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;
                },
            ),
            ...
        );
    }

}

Так что теперь я попробовал это (с простым throw new \Exception('foo')) в нескольких местах в коде (в Resource, в Service и в Mapper class) и ожидается, что исключения будут кэшированы и зарегистрированы в файле, для которого я определен. Но это не работает.

Я делаю что-то неправильно? Какие? Как заставить это работать? Как перехватить и зарегистрировать все исключения в приложении Zend Framework 2, управляемом Apigility?


Дополнительная информация: Пример места в коде, где генерируется исключение:

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

Дополнительная информация: трассировка в respose (если установлен throw new \Exception('fetch_EXCEPTION'); в BarResource#fetch(...)):

{
    "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"
}

3 ответа

В настоящее время мы успешно используем следующий код для сбора всех сообщений об ошибках от 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
    }
});

Вы подключили своего слушателя к MvcEvent::EVENT_DISPATCH_ERROR событие. Вы уверены, что бросаете new \Exception('foo') во время отправки (т.е. в контроллере). Также в ответе, на который вы ссылаетесь, они упоминают, что это решение предназначено для обнаружения ошибок / исключений, выдаваемых в контроллере.

Например, если вы выдаваете исключение во время рендеринга, ваш слушатель никогда не будет запущен. В этих случаях вам нужно будет слушать MvcEvent::EVENT_RENDER_ERROR,

Интересно, является ли эта установка лучшим способом сделать это? Возможно, вам следует искать другие примеры, а не просто следовать / копировать ответ из Stackru.

РЕДАКТИРОВАТЬ:

Если вы также используете ApiProblem модуль для Apigility, то это может быть ApiProblemListener срабатывает на MvcEvent::EVENT_DISPATCH_ERROR событие перед вашим собственным слушателем.

В onDispatchError метод ApiProblemListener возвращает объект ответа, и это может быть причиной того, что другие события (с более низким приоритетом) вообще не инициируются после этого.

ApiProblemListener прикреплен так:

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

Попробуйте повысить приоритет вашего слушателя до значения выше 100 от ApiProblemListener, Тогда у вас, вероятно, будет успех.

Я сталкиваюсь с той же проблемой, чтобы регистрировать любое исключение,

Я пытаюсь присоединить своего слушателя к MvcEvent::EVENT_DISPATCH_ERROR даже с высоким приоритетом [3000] не работает, после некоторых исследований и чтения Apigility код я обнаружил, что любое выброшенное исключение будет поймано ApiProblemListener и создать новый ApiProblem из этого исключения информации в onRender метод

Обходной путь для решения проблемы и регистрации любого исключения - переопределить AbstractResourceListener и создать свой собственный ResourceListener и заставить любой класс Resource расширять его,

твой собственный 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;
    }
}
Другие вопросы по тегам