Faciliter la maintenance d’une application Zend en combinant Zend_Log et ErrorController

Objectif

Que celui qui n’a pas perdu son temps à comprendre et reproduire un problème survenu sur une application en production me jette la première pierre !

Dans le contexte d’une application MVC basée sur le Zend Framework (architecture identique à celle du Quickstart de la version 1.9.4), voyons comment configurer son contrôleur d’erreurs pour journaliser les erreurs applicatives dans un fichier de log.

Environnement applicatif et configuration Apache

Il est de bon ton de définir dans le .htaccess de /monprojet/public ou dans le virtual host Apache (quand on a la main dessus), la constante de l’environnement de l’application :

SetEnv APPLICATION_ENV development
...

Ceci permet de changer simplement la configuration selon l’environnement d’exécution (ici seulement développement et production).

Cette constante est ensuite récupérée dans /monprojet/public/index.php :

defined('APPLICATION_ENV') || define('APPLICATION_ENV',
    (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production'));

Configurer le logger

Définir les options de journalisation dans le fichier /monprojet/application/configs/application.ini :

[production]
...
logging.enabled = 1
logging.level = 3
logging.filename = /tmp/exploit.log
[development : production]
...
logging.level = 7

Les niveaux de log par défaut sont les suivants, ici nous journaliserons donc les informations de niveau ERR, CRIT, ALERT, EMERG sur la production, et tous les niveaux sur l’environnement de dev :

EMERG   = 0;  // Emergency: system is unusable
ALERT   = 1;  // Alert: action must be taken immediately
CRIT    = 2;  // Critical: critical conditions
ERR     = 3;  // Error: error conditions
WARN    = 4;  // Warning: warning conditions
NOTICE  = 5;  // Notice: normal but significant condition
INFO    = 6;  // Informational: informational messages
DEBUG   = 7;  // Debug: debug messages

Reste à configurer le logger dans le bootstrap (/monprojet/application/Bootstrap.php) pour qu’il prenne en compte les options :

    protected function _initLogging()
    {
        $logger = new Zend_Log();
        // récupérer et filtrer sur le niveau de log
        $optionLevel = (int) $this->_options["logging"]["level"];
        $filter = new Zend_Log_Filter_Priority($optionLevel);
        $logger->addFilter($filter);
        // ajouter un rédacteur qui écrit dans le fichier défini
        $optionPath = $this->_options["logging"]["filename"];
        $writer = new Zend_Log_Writer_Stream($optionPath);
        $logger->addWriter($writer);
        Zend_Registry::set("logger", $logger);
    }

Personnaliser notre ErrorController

On complète juste l’action du QuickStart :

    public function errorAction()
    {
        $this->_helper->layout->setLayout('layouts/site');
        $errors = $this->_getParam('error_handler');
        switch ($errors->type) { 
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
                // 404 error -- controller or action not found
                $this->getResponse()->setHttpResponseCode(404);
                $this->view->message = 'Page not found';
                break;
            default:
                // application error 
                $this->getResponse()->setHttpResponseCode(500);
                $this->view->message = 'Application error';
                // logging
                $logger = Zend_Registry::get("logger");
                // logger le type d'exception et sa trace
                $logger->err($errors->exception->getMessage());
                $logger->err($errors->exception->getTraceAsString());
                // rediriger la sortie var_dump dans le fichier de log
                ob_start();
                var_export($errors->request->getParams());
                $formatedParams = ob_get_contents();
                ob_end_clean();
                $logger->err($formatedParams);
                break;
        }
        $this->view->exception = $errors->exception;
        $this->view->request   = $errors->request;
    }

Provoquer une erreur pour tester

Pour se faire, il suffit de lever une exception dans l’action index du contrôleur index, puis afficher l’accueil de l’application :

    public function indexAction()
    {
        // ...    
        throw new Exception('Division by zero.'); 
    }

Le fichier de /tmp/exploit.log contient désormais des informations permettant de reproduire l’anomalie que l’utilisateur aura rencontré :

2009-12-03T22:37:38+01:00 ERR (3): Division by zero.
2009-12-03T22:37:38+01:00 ERR (3): #0 /usr/share/php/Zend/Controller/Action.php(513): IndexController->indexAction()
#1 /usr/share/php/Zend/Controller/Dispatcher/Standard.php(289): Zend_Controller_Action->dispatch('indexAction')
#2 /usr/share/php/Zend/Controller/Front.php(946): Zend_Controller_Dispatcher_Standard->dispatch(Object(Zend_Controller_Request_Http), Object(Zend_Controller_Response_Http))
#3 /usr/share/php/Zend/Application/Bootstrap/Bootstrap.php(77): Zend_Controller_Front->dispatch()
#4 /usr/share/php/Zend/Application.php(358): Zend_Application_Bootstrap_Bootstrap->run()
#5 /var/www/monprojet/public/index.php(26): Zend_Application->run()
#6 {main}
2009-12-03T22:37:38+01:00 ERR (3): array (
  'controller' => 'index',
  'action' => 'index',
  'module' => 'default',
)

Conclusion

Malgré un développement soigné et des tonnes de tests unitaires et fonctionnels, l’utilisateur final se débrouillera toujours pour tomber, loi de Murphy oblige, sur un nouveau bug.

L’exploitation des logs permet de s’en rendre compte et de prendre les mesures qui s’imposent.

Il est également possible d’écrire ses logs en base de données et pourquoi pas de créer un petit module pour pouvoir les consulter et les classer par fréquence.

L’envoi par e-mail des erreurs critiques est aussi une bonne alternative, difficile de faire abstraction de ce genre de spam. :D

Ressources complémentaires

Zend Framework , , Permalien.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

*

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">