Как перехватить и зарегистрировать все исключения в приложении 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;
}
}