quarta-feira, 24 de fevereiro de 2010

Tratando exceções no ambiente de produção.

* ver o primeiro comentário para ajustar um bug de loop eterno na homepage

Objetivo: Logar qualquer exceção não tratada no código, mandar um email para a equipe, gravar um log e mostrar uma mensagem para o usuário do tipo: "Estamos em manutenção para melhor servir." enquanto a equipe corre pra consertar o erro.

Como Fazer

1 Primeiro conectamos um listener ao evento de exception. Inicialmente pensei em capturar isso com um filtro, mas vi que existem algumas exceções que são capturadas que atrapalham o bom funcionamento do framework, como por exemplo a ReflectionException que vai gerar um 404 mais na frente.

Usando um listener para o evento, capturamos apenas aquilo que o symfony não trata:


class frontendConfiguration extends sfApplicationConfiguration {
public function configure() {
$this->dispatcher->connect(
'application.throw_exception',
array('MyExceptionHandler', 'listenToApplicationException')
);
}
...


2 Depois criamos a classe que ser chamada, contendo o método que vai "ouvir" o evento, conforme determinamos acima:


class MyExceptionHandler extends sfFilter {
static public function listenToApplicationException($event)
{
// pegamos a exception original
$e = $event->getSubject();
// para passar um link na mensagem mostrada ao usuário
sfLoader::loadHelpers(array('Url'));
try {
// manda email e grava um log
self::notify($e);
}
catch(Exception $e) {
// "Engulindo" propositadamente os
// erros quando envia email de log
// para não impedir que a mensagem
// seja mostrada para o usuário
}
if(self::isAjax()) {
// lembre dos requests de ajax...
// tratar de acordo com o framework do freguês.
}
else {
sfContext::getInstance()->getUser()->setFlash(
'feedback',
sprintf(
Messages::UNEXPECTED_ERROR,
url_for('@support')
)
);
sfContext::getInstance()->getController()->redirect('@homepage');
}
// este evento específico chama cada listener até que um responda que pôde tratar o evento. Por isso esse retorno.
return true;
}

static function notify(Exception $e) {
// cria a mensagem...manda email e registra um log.
self::log($mensagem);
}

static function log($message) {
$exceptionLogLocation = sfConfig::get('sf_log_dir') . '/exceptions.log';
$fileHandle = fopen($exceptionLogLocation, "a+");
fwrite($fileHandle, $message."\n");
}

static function isAjax(){
return sfContext::getInstance()->getRequest()->isXmlHttpRequest() ||
sfContext::getInstance()->getRequest()->hasParameter('iframeAjaxUpload');

}
}


3 Feito. Fica aqui mais uma dica de algumas coisas que você pode capturar e colocar na mensagem para facilitar a vida depois:



$username = sfContext::getInstance()->getUser()->getService()->getUsername();
$referer = sfContext::getInstance()->getRequest()->getReferer();
$uri = sfContext::getInstance()->getRequest()->getUri();
$ajax = self::isAjax() ? 'Yes' : 'No';
'time' => date('Y-m-d H:i:s'),
'exception_class' => get_class($e),
'exception_message' => $e->getMessage(),
'trace' => $e->__toString()

5 comentários:

  1. Depois, testando isso achei um bug que é o seguinte: se a exceção é lançada na homepage, o sistema entra em loop.

    Então é necessário testar isso antes de redirecionar e, se for a home, mandar para uma página alternativa ou algo assim.

    ResponderExcluir
  2. Use algo do tipo:

    $module = $sfContext->getModuleName();
    $action = $sfContext->getActionName();
    if($module == 'home' && $action == 'index'){
    $sfContext->getController()->redirect($sfContext->getRequest()->getUriPrefix().'/maintenance.html');
    return true;


    Veja que o arquivo final é uma página estática. Isso é util porque impede de ficar em loop para a página inicial quando tem um erro geral, fora da action da home.

    ResponderExcluir
  3. acho que você poderia utilizar esse plugin: http://www.symfony-project.org/plugins/sfErrorNotifierPlugin

    ResponderExcluir
  4. legal, utilizo algo parecido também,

    ResponderExcluir
  5. Pedro, é uma idéia boa. Vou dar uma olhada. Por enquanto fico com esse por conta do frame que uso para ajax e lib de email.

    Nei, se vc quiser contribuir aqui com algumas idéias a mais, fique a vontade.

    ResponderExcluir