web-dev-qa-db-fra.com

Erreur de journalisation, de manière fluide

Je lisais en particulier sur «l'enregistrement des erreurs». Et j'ai mis au point la fonction «error_log», qui semble être un bon outil à utiliser pour gérer l'enregistrement des erreurs. Mais comment est la meilleure et la plus douce façon de l’utiliser?

Si j'ai un 

try {
     //try a database connection...

} catch (PDOException $e) {
    error_log($e->getMessage(), 3, "/var/tmp/my-errors.log");

}

Cela consignerait l'erreur dans le fichier my-errors.log. Mais que se passe-t-il si j’ai parfois besoin de changer la position du fichier, un nouveau dossier, ou quelque chose du genre? Si j'ai des tonnes de fichiers, je dois tous les changer.

Maintenant, j'ai commencé à penser à utiliser une variable pour définir le chemin d'accès au journal des erreurs. Bien sûr, cela pourrait fonctionner, mais que faire si je veux utiliser error_log dans une méthode ou une méthode de classe? Ensuite, je devrais définir la variable comme globale, mais cela est considéré comme une mauvaise pratique! Mais que faire si je ne devais pas utiliser la fonction en profondeur dans une classe, ne serait-ce pas aussi considéré comme une mauvaise pratique? Quelle est une bonne solution ici?

<?php

function legit() {
    try {
        if (1 == 1) {
            throw new Exception('There was an error here');
        }
    } catch (Exception $e) {
        throw new Exception('throw the error to the try-catch outside the function...');
    }

}

try {
    legit();
} catch (Exception $e) {
    echo 'error here' . $e->getMessage();

    //log it
}

Ceci est un exemple de ce dont je parlais ci-dessus (ne pas avoir la journalisation en profondeur dans une classe/fonction ... Est-ce un bon moyen?)

Plus loin:

Je ne sais pas trop comment utiliser les exceptions en général. Supposons que je souhaite effectuer une insertion dans une base de données avec SQL à l'intérieur d'une méthode. Est-ce que j'utiliserais un essai/un accrochage, puis une nouvelle exception si elle échoue? Est-ce considéré comme une bonne pratique? Des exemples s'il vous plaît.

37
John

Il a été demandé de rendre cette réponse plus applicable à un public plus large, alors allez-y.

Préambule

La gestion des erreurs n’est généralement pas la première chose à laquelle vous voudrez penser lors de l’écriture d’une application; il en résulte indirectement que le besoin s'en fait sentir. Cependant, exploiter les mécanismes existants dans PHP ne coûte pas cher non plus.

C'est un article assez long, je l'ai donc divisé en ensembles logiques de texte.

Erreurs de déclenchement

Dans PHP, les erreurs peuvent être déclenchées de deux manières différentes:

  1. Erreurs de PHP lui-même (utilisation de variables non définies, par exemple) ou de fonctions internes (par exemple, imagecreatefromjpeg n'a pas pu ouvrir un fichier),
  2. Erreurs déclenchées par le code utilisateur à l’aide de trigger_error ,

Celles-ci sont généralement imprimées sur votre page (sauf si display_errors est désactivé ou error_reporting vaut zéro), ce qui devrait être standard pour les machines de production, à moins que vous n'écriviez un code parfait comme moi ... à l'avenir. ces erreurs peuvent également être capturées, vous donnant un aperçu de tout accroc dans le code, en utilisant set_error_handler expliqué plus tard.

Lancer des exceptions

Les exceptions sont différentes des erreurs de trois manières principales:

  1. Le code qui les gère peut être très éloigné de l'endroit d'où ils sont lancés. L'état de la variable à l'origine doit être explicitement passé au constructeur d'exception, sinon vous ne disposez que de la trace de la pile.
  2. Le code entre l'exception et la capture est entièrement ignoré, alors qu'après une erreur (et que ce n'était pas fatal), le code continue toujours.
  3. Ils peuvent être étendus à partir de la classe principale Exception; Cela vous permet de capturer et de gérer des exceptions spécifiques tout en laissant les autres se disperser dans la pile jusqu'à ce qu'ils soient capturés par un autre code. Voir aussi: http://www.php.net/manual/fr/language.exceptions.php

Un exemple d'exceptions de lancement est donné plus tard.

Gestion des erreurs

La capture et la gestion des erreurs sont assez simples en enregistrant un gestionnaire d’erreurs, par exemple:

function my_error_handler($errno, $errstr, $errfile = 'unknown', $errline = 0, array $errcontext = array())
{
    // $errcontext is very powerful, it gives you the variable state at the point of error; this can be a pretty big variable in certain cases, but it may be extremely valuable for debugging
    // if error_reporting() returns 0, it means the error control operator was used (@)
    printf("%s [%d] occurred in %s:%d\n%s\n", $errstr, $errno, $errfile, $errline, print_r($errcontext, true));

    // if necessary, you can retrieve the stack trace that led up to the error by calling debug_backtrace()

    // if you return false here, the standard PHP error reporting is performed
}

set_error_handler('my_error_handler');

Pour démarrer, vous pouvez transformer toutes les erreurs en ErrorException en enregistrant le gestionnaire d'erreurs suivant (PHP> = 5.1):

function exception_error_handler($errno, $errstr, $errfile, $errline)
{
    throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
}

set_error_handler("exception_error_handler");

Gestion des exceptions

Dans la plupart des cas, vous gérez les exceptions le plus près possible du code qui l'a amené à autoriser les plans de sauvegarde. Par exemple, vous essayez d'insérer un enregistrement de base de données et une exception de contrainte de clé primaire est levée; vous pouvez récupérer en mettant à jour l’enregistrement (ce qui est conçu comme la plupart des bases de données peuvent gérer cela tout seul). Certaines exceptions ne peuvent tout simplement pas être gérées localement, vous voulez donc que celles-ci soient transférées en cascade. Exemple:

function insertRecord($user, $name)
{
    try {
        if (true) {
            throw new Exception('This exception should not be handled here');
        }
        // this code is not executed
        $this->db->insert('users', array('uid' => $user, 'name' => $name));
    } catch (PDOException $e) {
        // attempt to fix; an exception thrown here will cascade down
        throw $e; // rethrow exception

        // since PHP 5.3.0 you can also nest exceptions
        throw new Exception("Could not insert '$name'", -1, $e);
    } catch (WhatEverException $e) {
        // guess what, we can handle whatever too
    }
}

L'exception glissante

Alors que se passe-t-il lorsque vous ne détectez pas d'exception nulle part? Vous pouvez aussi comprendre cela en utilisant set_exception_handler .

function my_exception_handler(Exception $exception)
{
    // do your stuff here, just don't throw another exception here
}

set_exception_handler('my_exception_handler');

Cela n'est pas recommandé, à moins que vous n'ayez aucun moyen significatif de gérer l'exception dans votre code.

Consignation de l'erreur/exception

Maintenant que vous gérez l'erreur, vous devez la consigner quelque part. Pour mon exemple, j'utilise un projet porté par Apache de Java à PHP, appelé LOG4PHP . Il y en a d'autres, mais cela illustre l'importance d'une installation de journalisation flexible.

Il utilise les concepts suivants:

  1. Enregistreurs - entités nommées qui effectuent la journalisation en votre nom; ils peuvent être spécifiques à une classe de votre projet ou partagés en tant que logger commun,
  2. Appendeurs - chaque demande de journal peut être envoyée à une ou plusieurs destinations (courrier électronique, base de données, fichier texte) en fonction de conditions prédéfinies (telles que le niveau de journalisation),
  3. Niveaux - les journaux sont classés à partir des messages de débogage en erreurs fatales.

Utilisation de base pour illustrer différents niveaux de message:

Logger::getLogger('main')->info('We have lift off');
Logger::getLogger('main')->warn('Rocket is a bit hot');
Logger::getLogger('main')->error('Houston, we have a problem');

En utilisant ces concepts, vous pouvez modéliser une installation de journalisation assez puissante. Par exemple, sans changer le code ci-dessus, vous pouvez implémenter la configuration suivante:

  1. Recueillir tous les messages de débogage dans une base de données pour que les développeurs les consultent; vous pouvez le désactiver sur le serveur de production,
  2. Recueillez les avertissements dans un fichier quotidien que vous pourriez envoyer par courrier électronique à la fin de la journée.
  3. Avoir des emails immédiats sur des erreurs fatales.
35
Ja͢ck

En outre, pour la journalisation des erreurs (et en fait pour toutes les journalisations), j'utiliserais le répartiteur d'événements, de la même manière que le framework Symfony.

Jetez un coup d'oeil à ce composant sf (sa dépendance très légère, toute la structure n'est pas nécessaire, il y a peut-être 3 classes php pertinentes et 2 interfaces)

https://github.com/symfony/EventDispatcher

de cette façon, vous pouvez créer un répartiteur quelque part dans votre application bootstrap:

use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\Event;

$dispatcher = new EventDispatcher();

//register listeners
$dispatcher->addListener('application.log', function (Event $event) {
    //do anything you want
});

Ensuite, vous pouvez soulever un événement à n’importe quel endroit de votre code, par exemple:

$dispatcher->dispatch(new GenericEvent('application.log', array('message' => 'some log', 'priority' => 'high'));

Bien entendu, vous pouvez sous-classer une classe d'événements avec vos propres événements:

class LogEvent extends GenericEvent {
    public function __construct($message, $priority = 'INFO') {
        parent::__construct('application.log', array('message'=>$message,'priority'=>$priority));
    }
    public function getMessage() { return $this->getArgument('message'); }
    public function getPriority() { return $this->getArgument('priority'); }
}

// now raising LogEvent is much cleaner:
$dispatcher->dispatch(new LogEvent('some log'));

Cela vous permettra également de créer des événements plus personnalisés, tels que ExceptionEvent.

 class ExceptionEvent extends GenericEvent {
    public function __construct(Exception $cause) {
        parent::__construct('exception.event', array('cause' => $cause));
    }
 }

Et manipulez-les en conséquence.

Avantages

  • vous séparez la logique de journalisation de votre application
  • vous pouvez facilement ajouter et supprimer des enregistreurs au moment de l'exécution
  • vous pouvez facilement enregistrer autant d'enregistreurs que vous voulez (c'est-à-dire DebugLogger qui enregistre tout dans un fichier texte, ErrorLogger qui enregistre uniquement les erreurs dans error_log, CriticalLogger qui enregistre uniquement les erreurs critiques dans l'environnement de production et les envoie par courrier électronique à l'administrateur, etc.).
  • vous pouvez utiliser le répartiteur d'événements pour d'autres tâches que la journalisation (en fait, pour chaque travail pour lequel un modèle d'observateur est approprié)
  • l'enregistreur réel devient tout simplement un "détail d'implémentation" - il est si facile à remplacer que la destination de vos journaux importe peu - vous pourrez remplacer la destination du journal à tout moment sans avoir à refactoriser les noms de vos méthodes ou à modifier quoi que ce soit dans du code.
  • il sera facile d'implémenter une logique de routage de journal complexe ou de changer globalement le format du journal (en configurant les enregistreurs)
  • tout devient encore plus flexible si vous utilisez l'injection de dépendance pour les écouteurs (enregistreurs) et le répartiteur (dans les classes qui notifient l'événement de journalisation)

Enregistrement actuel

Comme quelqu'un l'a déjà dit, je conseillerais d'utiliser une bibliothèque prête à l'emploi, comme Monolog, Zend_Log ou log4php, il n'y a probablement aucune raison de coder ces choses à la main (et la dernière chose que vous voulez est un enregistreur d'erreurs cassé !)

PS: Traitez les extraits de code comme des pseudo-codes, je ne les ai pas testés. Les détails peuvent être trouvés dans la documentation des bibliothèques mentionnées.

3
Mariusz Sakowski

Définissez-le, puis utilisez-le :)

define('ERRORLOG_PATH', '/var/tmp/my-errors.log');

error_log($e->getMessage(), 3, ERRORLOG_PATH);

Alternativement, il suffit de rendre le troisième paramètre de error_log facultatif, en le configurant par défaut sur le chemin souhaité.

3
Tom van der Woerdt

Si vous avez toujours besoin d'une méthode personnalisée de gestion des journaux (par exemple, vous ne souhaitez pas utiliser la fonction trigger_error() standard), je vous conseillerais de consulter Zend_Log ( http://framework.zend.com/manual/en/zend.log .overview.html ) pour ces raisons:

  1. ceci peut être utilisé en tant que composant autonome, ZF n'est pas une infrastructure complète. Vous ne pouvez copier que les espaces de noms Zend_Loader et Zend_Log, instancier Zend_Loader et l'utiliser. Voir ci-dessous:

    require_once('Zend/Loader/Autoloader.php');
    
    $loader = Zend_Loader_Autoloader::getInstance();
    
    $logger = new Zend_Log();    
    $writer = new Zend_Log_Writer_Stream('php://output');
    
    $logger->addWriter($writer);    
    $logger->log('Informational message', Zend_Log::INFO);
    
  2. De nombreuses bibliothèques de journalisation vous ont été proposées, mais je pense que l'équipe de Zend (fondateurs de PHP lang) sait ce qu'elle fait.

  3. Vous pouvez utiliser n'importe quel rédacteur (base de données, STDOUT - voir ci-dessus, fichier, peu importe, vous pouvez le personnaliser pour écrire le vôtre afin de publier des messages de journal sur un service Web).
  4. log niveaux
  5. peut changer le format du journal (mais celui qui est prêt à l’emploi est excellent dans mon esprit). L'exemple ci-dessus avec un formateur standard produira quelque chose comme ceci:

2012-05-07T23: 57: 23 + 03: 00 INFO (6): Message d'information

  1. il suffit de lire la référence, il peut être configuré pour intercepter les erreurs php
3
Alexey

Comme le dit KNL, ce qui est tout à fait correct, mais malheureusement non documenté, avoir des erreurs lors du lancement d’exceptions n’est pas recommandé par les développeurs de PHP et que quelqu'un ait commis une erreur dans la documentation. Cela peut en effet causer des bugs avec de nombreuses extensions, alors ne le faites pas.

Cela a déjà été débattu sur #PHP sur irc.

Le "Cependant, les erreurs peuvent être simplement traduites en exceptions avec ErrorException." sur http://php.net/manual/en/language.exceptions.php va être supprimé.

0
user937635

Je choisirais la solution de journalisation Tom vand der Woerdt, la plus simple et la plus efficace pour vos besoins.

Quant à l'autre question:

Vous n'avez pas besoin d'attraper/de rediffuser l'exception à l'intérieur de la fonction, sauf s'il existe un type d'exception spécifique pour lequel vous avez une solution.

Exemple quelque peu simpliste:

define('ERRORLOG_PATH', '/var/tmp/my-errors.log');
function do_something($in)
{
    if (is_good($in))
    {
        try {
            return get_data($in);
        } catch (NoDataException $e) {
            // Since it's not too big a deal that nothing
            // was found, we just return false.
            return false;
        }
    } else {
        throw new InvalidArguementException('$in is not good');
    }
}

function get_data($data)
{
    if (!is_int($data))
    {
        InvalidArguementException('No');
    }
    $get = //do some getting.
    if (!$get)
    {
        throw new NoDataException('No data was found.');
    } else {
        return $get;
    }
}

try {
   do_something('value');
} catch (Exception $e) {
   error_log($e->getMessage(), 3, ERRORLOG_PATH);
   die ('Something went wrong :(');
}

Ici, vous ne récupéreriez que la variable NoDataException car toutes les autres erreurs relèvent de la première et restaient traitées par la logique supérieure, car toutes les exceptions levées devaient hériter de la variable Exception.

Évidemment, si vous lancez à nouveau une Exception (en dehors du try {} initial ou du catch {} supérieur), votre script se terminera avec une erreur d'exception non capturée et la journalisation des erreurs sera perdue.

Si vous voulez aller jusqu'au bout, vous pouvez également implémenter une fonction de gestion des erreurs personnalisée à l'aide de set_error_handler() et y mettre également votre connexion.

0

J'utilise ma propre fonction qui me permet d'écrire plusieurs types de fichiers journaux en définissant ou en modifiant le deuxième paramètre.

Je dépasse les questions conceptuelles que vous posez sur "quelle est la bonne manière" de le faire, en incluant la fonction log dans une bibliothèque de fonctions que je considère "natives" dans mes projets de développement ... De cette façon, je peux envisager ces fonctions font simplement partie de "MON" noyau php, comme date() ou time()

Dans cette version de base de dlog, je gère également les tableaux. alors que je l’utilisais à l’origine pour consigner les erreurs, j’ai fini par l’utiliser pour d’autres suivis à court terme «rapides et sales», tels que la consignation des heures de saisie du code dans une section donnée, la connexion des utilisateurs, etc.

function dlog($message,$type="php-dlog")
{
    if(!is_array($message)  )
        $message=trim($message);
    error_log(date("m/d/Y h:i:s").":".print_r($message,true)."\n",3, "/data/web/logs/$_SERVER[HTTP_Host]-$type.log");
} 
0
Michael Blood

Si le moyen de gérer les erreurs PHP n'est pas assez souple pour vous (par exemple, vous souhaitez parfois vous connecter à une base de données, parfois à un fichier, parfois autre chose), vous devez utiliser/créer une journalisation PHP personnalisée. cadre. 

Vous pouvez parcourir la discussion dans https://stackoverflow.com/questions/341154/php-logging-framework ou tout simplement et donner le premier choix, KLogger , essayez. Je ne suis toutefois pas sûr s'il prend en charge les destinations personnalisées pour la journalisation. Mais à tout le moins, il s’agit d’un petit groupe facile à lire et vous devriez pouvoir l’étendre davantage pour répondre à vos propres besoins.

0
Aurimas

Il y a deux défis à relever. La première consiste à faire preuve de souplesse lors de la connexion à différents canaux. Dans ce cas, vous devriez par exemple regarder Monolog .

Le deuxième défi consiste à intégrer cette connexion à votre application. À mon avis, le mieux est de ne pas utiliser explicitement la journalisation. Ici, par exemple l'orientation de l'aspect est très pratique. Un bon échantillon est flow3 .

Mais il s’agit plus d’une vue à vol d'oiseau sur le problème ...

0
Broncko

La plupart des enregistreurs d'erreur et des enregistreurs d'exceptions sont inutiles pour la plupart des utilisateurs car ils n'ont pas accès aux fichiers journaux.

Je préfère utiliser un gestionnaire d'erreurs personnalisé et un gestionnaire d'exceptions personnalisé et que ceux-ci, lors de la production, enregistrent les erreurs directement dans la base de données si le système s'exécute sur une base de données. 

Pendant le développement, lorsque display_errors est défini, il ne consigne rien car toutes les erreurs sont soulevées dans le navigateur.

Et en guise de remarque: ne faites pas de votre gestionnaire d'erreurs personnalisé une exception! C'est une très mauvaise idée. Cela peut causer des bugs dans le gestionnaire de mémoire tampon et dans certaines extensions. De plus, certaines fonctions PHP principales telles que fopen () provoquent un avertissement ou un avis d'échec; elles doivent être traitées en conséquence et ne doivent pas arrêter l'application si une exception le ferait.

La mention d'avoir le gestionnaire d'erreurs levant des exceptions dans la documentation de PHP est un bogue de note.

0
user1547243